Wîkîferheng
kuwiktionary
https://ku.wiktionary.org/wiki/Destp%C3%AAk
MediaWiki 1.47.0-wmf.12
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
Juli
0
57771
6291626
6117930
2026-07-27T06:18:21Z
Jeuwre
4694
Almanî /* Bilêvkirin */
6291626
wikitext
text/x-wiki
== {{ziman|nds-de}} ==
=== Navdêr ===
{{navdêr|nds-de|z=n}}
# {{mijar|nds-de|mehên salê}} {{g|ku|tîrmeh}}, {{g|ku|temûz}} {{şerh|meha salê ya heftan}}
=== Çavkanî ===
{{rûpela wergerê|nds-de|tîrmeh}}
== {{ziman|de}} ==
=== Bilêvkirin ===
* {{IPA|de|[ˈjuːli]}}
* {{deng|de|De-Juli2.ogg|Deng|dever=Berlîn, Almanya}}
* {{deng|de|De-Juli.ogg|Deng}}
* {{deng|de|LL-Q188 (deu)-Sebastian Wallroth-Juli.wav|Deng|dever=Berlîn, Almanya}}
* {{deng|de|LL-Q188 (deu)-Natschoba-Juli.wav|Deng|dever=Almanya}}
* {{kîte|de|Ju|li}}
=== Navdêr ===
{{navdêr|de|z=n}}
# [[tîrmeh]]
== {{ziman|stq}} ==
=== Navdêr ===
{{navdêr|stq}}
# {{mijar|stq|mehên salê}} {{g|ku|tîrmeh}}, {{g|ku|temûz}} {{şerh|meha salê ya heftan}}
=== Çavkanî ===
{{rûpela wergerê|stq|tîrmeh}}
== {{ziman|li}} ==
=== Navdêr ===
{{navdêr|li}}
# {{mijar|li|mehên salê}} {{g|ku|tîrmeh}}, {{g|ku|temûz}} {{şerh|meha salê ya heftan}}
=== Çavkanî ===
{{rûpela wergerê|li|tîrmeh}}
== {{ziman|lb}} ==
=== Navdêr ===
{{navdêr|lb|z=n}}
# {{mijar|lb|mehên salê}} {{g|ku|tîrmeh}}, {{g|ku|temûz}} {{şerh|meha salê ya heftan}}
=== Çavkanî ===
{{rûpela wergerê|lb|tîrmeh}}
== {{ziman|pdt}} ==
=== Navdêr ===
{{navdêr|pdt|z=n}}
# {{mijar|pdt|mehên salê}} {{g|ku|tîrmeh}}, {{g|ku|temûz}} {{şerh|meha salê ya heftan}}
=== Çavkanî ===
{{rûpela wergerê|pdt|tîrmeh}}
0iqxesgnco7h33llwe46vs00lbxhncn
MediaWiki:Newarticletext
8
118589
6291597
6111483
2026-07-26T20:06:51Z
Wikihez
7344
6291597
wikitext
text/x-wiki
{{#switch:{{NAMESPACENUMBER}}<!--
-->|14= {{tabloya kategorîkirinê}}<!--
-->|#default=<div class="plainlinks" style="background: var(--wikt-palette-paleblue, #F9F9F9); border: 1px solid var(--wikt-palette-lightgrey, #aaa); padding-left: 12px; margin-bottom: 5px">
'''Ev rûpel hîn tine.'''
* Eger tu bixwazî vê rûpelê çêkî, dest bi nivîsandinê bike û piştre qeyd bike. '''Wêrek be''', biceribîne!<br />
* Ji bo alîkariyê binêre: [[{{MediaWiki:Helppage}}|Alîkarî]].<br />
* Heke tu bi xeletî hatî, vegere rûpela berê.
</div>
}}
1s52zmm3s8gkv8ks8e3l9qxgg5xgj6v
6291598
6291597
2026-07-26T20:07:46Z
Wikihez
7344
6291598
wikitext
text/x-wiki
{{#switch:{{NAMESPACENUMBER}}<!--
-->|14= {{tabloya kategorîkirinê}}<!--
-->|#default=<div class="plainlinks" style="background: var(--wikt-palette-paleblue, #F9F9F9); border: 1px solid var(--wikt-palette-lightgrey, #aaa); color: var(--wikt-palette-black, #202122); padding-left: 12px; margin-bottom: 5px">
'''Ev rûpel hîn tine.'''
* Eger tu bixwazî vê rûpelê çêkî, dest bi nivîsandinê bike û piştre qeyd bike. '''Wêrek be''', biceribîne!<br />
* Ji bo alîkariyê binêre: [[{{MediaWiki:Helppage}}|Alîkarî]].<br />
* Heke tu bi xeletî hatî, vegere rûpela berê.
</div>
}}
7uoa5zkcpc8wv7a9pz9hf7xvlfnstj9
anafor
0
122516
6291561
4605196
2026-07-26T13:34:15Z
Wikihez
7344
/* Navdêr */
6291561
wikitext
text/x-wiki
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# [[gêrav]],[[gerînek]], [[ger]],[[gêreba]]
# {{e|tr|argo}} [[tişt|tiştê]] ku [[bêked|bêked]] û [[hed|hed]] û [[bi awayekî]] [[nelirê|nelirê]] [[hatin|hatiye]] [[bidestxistin]]
svql23bb18sxg6sta3bshd8rlksjxb5
hematolojî
0
296512
6291623
6282292
2026-07-26T23:41:49Z
Wikihez
7344
6291623
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{ku-tewîn-nav|hematolojî|mê}}
{{navdêr|ku|z=m}}
# [[xwînnasî]], [[vekolîn]]ên [[bijîşkî]] li ser [[xwîn]]a [[nexweş]]an
#* {{jêder|ku|jêgirtin=Di encama pêşbirkê de pêşniyara kilînîka '''hematolojiyê''' ya "Agahdariya pirzimanî û mûzîk" serkeftî bû.|www.aa.com.tr/kk 10/2013}}
==== Formên alternatîv ====
* [[hêmatolojî]]
=== Têkilîdar ===
* [[hematolog]]
* [[hemoglobîn]]
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W-|af|hematologie}}
* {{Z|sq}}: {{W-|sq|hematologjia|m}}
* {{Z|de}}: {{W-|de|Hämatologie|m}}
* {{Z|ast}}: {{W-|ast|hematoloxía|m}}
* {{Z|az}}: {{W-|az|hematologiya}}
* {{Z|eu}}: {{W+|eu|hematologia}}
* {{Z|ba}}: {{W-|ba|гематология}}
* {{Z|be}}: {{W-|be|гематалогія|m}}
* {{Z|bg}}: {{W-|bg|хематология|m}}
* {{Z|cs}}: {{W+|cs|hematologie|m}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|血液學|tr=hyut3 jik6 hok6}}
*: {{Z|cmn}}: {{W-|cmn|血液學|tr=xuèyèxué}}
* {{Z|da}}: {{W-|da|hæmatologi|g}}
* {{Z|id}}: {{W+|id|hematologi}}
* {{Z|ar}}: {{W-|ar|عِلْم الدَّمْ}}
* {{Z|hy}}: {{W+|hy|արյունաբանություն}}
* {{Z|eo}}: {{W-|eo|hematologio}}
* {{Z|fa}}: {{W-|fa|خونشناسی|tr=xun-šenâsi}}, {{W-|fa|هماتولوژی|tr=hemâtoloži}}
* {{Z|fi}}: {{W+|fi|hematologia}}, {{W+|fi|veritautioppi}}
* {{Z|fr}}: {{W+|fr|hématologie|m}}
* {{Z|gl}}: {{W-|gl|hematoloxía|m}}
* {{Z|ka}}: {{W-|ka|ჰემატოლოგია}}
* {{Z|hi}}: {{W-|hi|रक्तशास्त्र}}
* {{Z|nl}}: {{W+|nl|hematologie|m}}
* {{Z|io}}: {{W+|io|hematologio}}
* {{Z|en}}: {{W+|en|haematology}}
* {{Z|ga}}: {{W-|ga|haemaiteolaíocht|m}}
* {{Z|it}}: {{W+|it|ematologia|m}}
* {{Z|is}}: {{W+|is|blóðfræði|m}}
* {{Z|ja}}: {{W-|ja|血液学|tr=けつえきがく, ketsuekigaku|sc=Jpan}}
* {{Z|jv}}: {{W-|jv|hematologi}}
* {{Z|ca}}: {{W+|ca|hematologia|m}}
* {{Z|ky}}: {{W-|ky|гематология}}
* {{Z|ko}}: {{W+|ko|혈액학|sc=Kore}}
* {{Z|la}}: {{W-|la|haematologia|m}}
* {{Z|lv}}: {{W-|lv|hematoloģija|m}}
* {{Z|li}}: {{W+|li|bloodslieër|m}}
* {{Z|lt}}: {{W-|lt|hematologija|m}}
* {{Z|mk}}: {{W-|mk|хематологија|m}}
* {{Z|ml}}: {{W-|ml|ഹീമറ്റോളജി}}
* {{Z|ms}}: {{W+|ms|hematologi}}, {{W-|ms|kaji darah}}
* {{Z|mi}}: {{W-|mi|mātai toto}}
* {{Z|hu}}: {{W+|hu|hematológia}}
* {{Z|nds}}:
*: {{Z|nds-de}}: {{W-|nds-de|Hämatologie|m}}
* {{Z|new}}: {{W-|new|हेमाटोलोजी}}
* {{Z|no}}:
*: {{Z|nb}}: {{W-|nb|hematologi|n}}
*: {{Z|nn}}: {{W-|nn|hematologi|n}}
* {{Z|oc}}: {{W-|oc|ematologia|m}}
* {{Z|pl}}: {{W+|pl|hematologia|m}}
* {{Z|pt}}: {{W+|pt|hematologia|m}}
* {{Z|kk}}: {{W-|kk|гематология}}
* {{Z|ro}}: {{W+|ro|hematologie|m}}
* {{Z|ru}}: {{W+|ru|гематоло́гия|m}}
* {{Z|sa}}: {{W-|sa|रक्तशास्त्रम्}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|хематоло̀гија|m}}
*: Latînî: {{W+|sh|hematològija|m}}
* {{Z|sk}}: {{W+|sk|hematológia|m}}
* {{Z|sl}}: {{W+|sl|hematologija|m}}
* Sorbî:
*: {{Z|dsb}}: {{W-|dsb|hematologija|m}}
*: {{Z|hsb}}: {{W-|hsb|hematologija|m}}
* {{Z|es}}: {{W+|es|hematología|m}}
* {{Z|sv}}: {{W-|sv|hematologi|g}}
* {{Z|tg}}: {{W+|tg|хуншиносӣ}}
* {{Z|tl}}: {{W-|tl|paladuguan}}, {{W-|tl|hematolohiya}}
* {{Z|th}}: {{W-|th|โลหิตวิทยา|tr=loh-hìt-wít-tá-yaa}}
* {{Z|tt}}: {{W-|tt|гематология}}
* {{Z|tr}}: {{W+|tr|kan bilimi}}, {{W+|tr|hematoloji}}
* {{Z|uk}}: {{W-|uk|гематологія|m}}
* Uzbek:
*: Kirîlî: {{W+|uz|гематология}}
*: Latînî: {{W+|uz|gematologiya}}
* {{Z|vi}}: {{W+|vi|huyết học}}
* {{Z|war}}: {{W-|war|hematolohiya}}
* {{Z|cy}}: {{W-|cy|haematoleg|m}}
* {{Z|el}}: {{W+|el|αιματολογία|m}}
{{werger-bin}}
:{{werger çavkanî|en|haematology|beş=Ev qismê Wergerê|oldid=75897409}}
74ds7s9xu5klzs6pq5zcan5qotby6bk
bowl
0
362589
6291569
6092967
2026-07-26T15:14:23Z
Wikihez
7344
/* Navdêr */
6291569
wikitext
text/x-wiki
== {{ziman|en}} ==
[[Wêne:Bowl MET DP243440 (cropped).jpg|thumb|Kaseyeke çînî ya Misra Kevnare, teqrîben ji salên 200–150 BM]]
[[Wêne:Bowl (jatte à anses relevées or jatte écuelle) MET DP169254 (cropped).jpg|thumb|Kaseyeke neoklasîk ya fransî (jatte à anses relevées or jatte écuelle), teqrîben ji salên 1787-1788]]
=== Bilêvkirin ===
* {{mj|KY}} {{IPA|en|/bəʊl/|[bɒʊɫ]}}
* {{mj|NZ|Awistralya}} {{IPA|en|/bɐʉl/|[bɒʊɫ]}}
* {{mj|DYA}} {{IPA|en|/boʊl/|[boɫ]}}
* {{deng|en|en-us-bowl.ogg|dever=DYA}}
* {{deng|en|LL-Q1860 (eng)-Nattes à chat-bowl.wav|Deng|dever=}}
* {{deng|en|LL-Q1860 (eng)-Samuel Muldoon (Muldoonsamuel55)-bowl.wav|Deng|dever=Aurora, DYA}}
=== Navdêr ===
{{en-navdêr}}
# [[kasik]], [[kase]] {{îzah|[[qab|qabeke]] [[xwarin|xwarinê]] yê bi [[şikl|şiklê]] [[nîv|nîv]]-[[kure]] yê ku [[ji bo]] [[xwarin|xwarinan]] [[girtin|bigire]] [[nav|nava]] [[xwe|xwe]], ji bo [[tevlihevkirin|tevlihevkirina]] xwarinan an ji bo [[servîskirin|servîskirina]] wan tê [[bikaranîn]], [[wekî|wekî]] [[nimûne|nimûne]] [[salate|salate]], [[fêkî|fêkî]] an [[şorbe|şorbe]] an [[tişt|tiştên]] [[din|din]] [[xistin|dixine]] [[navê|navê]]}}
# Bi [[miqdar|miqdara]] [[kase|kaseyekê]].
#: {{hevmane|en|bowlful}}
#: {{mk|en|You can’t have any more soup – you’ve had three '''bowls''' already.|Tu nikarî êdî zêdetir şorbe vexwî, te jixwe bi qasî sê kaseyan vexwariye.}}
# {{f|en|çêkirina xwarinê}} [[xwarin|Xwarineke]] ku ji [[têkilkirin|têkilkirina]] [[çend|çend]] [[xwarin|xwarinên]] [[cihê]] [[pêk hatin|pêk tê]], ku ne [[hewce|hewceyê]] [[temamî|temamiya]] wan were [[pijandin]], û di kaseyekê de tên [[servîs kirin|servîskirin]].
# [[tiraş|Tiraş]]eke [[por]]ê yê ku porên [[rast|rast]] ji [[kinar|kinara]] bi [[eynî]] [[dirêjahî|dirêjahiyê]] tên [[qusandin]] û şiklê kaseyekê [[derhatin|dertê]] holê.
#: {{hevmaneyên peyvê|en|bowl cut|pudding bowl}}
# [[qism|Qismê]] [[vala]] yê [[gilover]] yê her tiştekî.
#: {{mk|en|Direct the cleaning fluid around the toilet '''bowl''' and under the rim.|Aliyê şilaviya paqijkirinê bidin ber bi derdora qulika tiwaletê û binê kinarê.}}
## Qismê [[kevçî|kevçiyê]] yê ku tiştan [[hilgirtin|hildigire]], [[zidd|ziddê]] qismê [[destik|destikê]] (ku ti tiştî [[girtin|nagire]] nava xwe).
## Qismeke [[qelûn]]ê an [[nargîle]]yê ku ji bo [[kêşan|kêşanê]] bi [[marijuana]] ([[çeşîd|çeşîdeke]] [[esrar|esrarê]]) hatiye tijîkirin.
## {{f|en|tîpografî}} [[parçe|Parçeyek]] [[gilover|gilover]] a [[glîf|glîfê]] ku [[cih|cihê]] [[vala|vala]] [[peçandin|dipêçe]], wekî di [[herf|herfên]] "d" û "o"yê de.
# {{f|en|landforms}} [[krater|Krater]]eke gilover (an wekî wê) yê di [[zemîn|zemînê]] de.
#: {{hevmaneyên peyvê|en|crater|hollow}}
# {{f|en|spor|tiyatro}} [[stadyum|Stadyûm]] an [[amfîtiyatro|amfîtiyatroyeke]] bi şiklê [[elîpsê|elîpsê]] yê ku kaseyê tîna bîra mirov.
# {{f|en|futbola amerîkî}} [[pêşbazî|Pêşbaziyek]] [[futbol|futbolê]] yê [[piştî]] [[sezon|sezonê]] (wekî nimûne ''Rose Bowl'', ''Super Bowl'')
==== Jê ====
{{kol3|en
|begging bowl
|[[bowl cut]]
|bowl game
|bowl lute
|coffee bowl
|dustbowl
|fruit bowl
|mixing bowl
|pudding bowl
|salad bowl
|singing bowl
|soup bowl
|toilet bowl
|bowl barrow|bowl chair|bowl hat|bowl of cherries|bowl pack|bowl wax|Buddha bowl|clean bowl|doggy dinner bowl look|finger bowl|fish bowl|food bowl|goldfish bowl|iron rice bowl|lawn-bowl|mazer bowl|poké bowl|pudding-bowl|punch bowl|punch bowl waterfall|quiz bowl|rice bowl|scorpion bowl|shampoo bowl|shit bowl|shit-bowl|slop bowl|slop-bowl|spaghetti bowl|spoon bowl|spoon-bowl|sugar bowl|swill-bowl|take the browns to the super bowl|tea bowl|track and bowl system|volcano bowl|work bowl|Zuni bowl}}
==== Etîmolojî ====
Ji {{mîr|enm|en|bolle}}, ji {{mîr|ang|en|bolla}}, {{qal|ang|bolle||kase}}, ji {{mîr|gmw-pro|en|*bollā}}, ji {{mîr|gem-pro|en|*bullǭ||top, amana gilover}}.
Bide ber {{hv|frr|bol}}, {{hv|gml|bolle}}, {{qal|gml|bole||objeya gilover}}, {{hv|nl|bol||top, kure}}, {{hv|de|Bolle}}, {{hv|da|bolle}}, {{hv|is|bolli}}. {{cotreh|en|boule|pulla}}.
==== Dûnde ====
* {{dû|bi|bol}}
* {{dû|eo|bovlo|deyn=1}}
* {{dû|fr|bol|deyn=1}}
* {{dû|de|Bowle|Bowl|deyn=1}}
** {{dû|hu|bólé|deyn=1}}
* {{dû|io|bolo|deyn=1}}
* {{dû|ga|bolla|deyn=1}}
* {{dû|ja|ボール|tr=bōru|deyn=1}}
* {{dû|lv|bole|deyn=1}} {{mj|muhtemelen ji almanî}}
* {{dû|ko|볼|deyn=1}}
* {{dû|gd|bòl|deyn=1}}
* {{dû|sv|bål|deyn=1}}
=== Navdêr ===
{{en-navdêr}}
# [[gog|Gog]]a ku di lîstika ''lawn bowls''-ê de ji aliyê lîstikvanan ve tê gindirandin.
# {{f|en|di pirjimariyê de|lê bi lêkereke yekjimar tê bikaranîn}} Lîstikek e.
#: {{hevmaneyên peyvê|en|lawn bowls|lawn bowling}}
==== Jê ====
{{kol2|en|bowl-off|bowl-out}}
==== Etîmolojî ====
Ji {{mîr|enm|en|bowle}}, {{qal|enm|boule}}, ji {{ji|fro|en|boule||ball}}, ji {{ji|la|en|bulla||bubble, stud, round object}}. {{cotreh|en|poll}}.
=== Lêker ===
{{en-lêker}}
# {{f|en|gh}} Bi şikleke rast topê [[gindirandin]] an [[avêtin]], di lîstika krîketê de an di lîstik û sporên mîna wê de.
# {{f|en|ngh}} Ji bo avêtina topê (di krîketê de û di lîstik û sporên wekî wê de).
# {{f|en|ngh}} Lîstina bowlîngê an lîstikeke wek wê.
# Bi rehetî û hêsanî bar kirin an gindirandin, wekî ku li ser tekerekê be.
#: {{mk|en|We were '''bowled''' rapidly along the road.|Em bi lez û bez li ser rê gindirîbûn.}}
# Li tişekî dan ku digere an digindire.
==== Jê ====
{{kol3|en
|bowl a googly|
|bowl along
|bowler
|bowling
|bowling alley
|bowling ball
|bowl out
|bowl over
}}
==== Dûnde ====
* {{dû|da|bowle|deyn=1}}
* {{dû|nl|bowlen|deyn=1}}
* {{dû|de|bowlen|deyn=1}}
==== Anagram ====
* {{anagram|en|a=blow|Blow|b'low|blow}}
8b0hnnx3ep86op9vo3wy1qzed55hpys
6291570
6291569
2026-07-26T15:14:40Z
Wikihez
7344
/* */ bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re
6291570
wikitext
text/x-wiki
== {{ziman|en}} ==
[[Wêne:Bowl MET DP243440 (cropped).jpg|thumb|Kaseyeke çînî ya Misra Kevnare, teqrîben ji salên 200–150 BM]]
[[Wêne:Bowl (jatte à anses relevées or jatte écuelle) MET DP169254 (cropped).jpg|thumb|Kaseyeke neoklasîk ya fransî (jatte à anses relevées or jatte écuelle), teqrîben ji salên 1787-1788]]
=== Bilêvkirin ===
* {{mj|KY}} {{IPA|en|/bəʊl/|[bɒʊɫ]}}
* {{mj|NZ|Awistralya}} {{IPA|en|/bɐʉl/|[bɒʊɫ]}}
* {{mj|DYA}} {{IPA|en|/boʊl/|[boɫ]}}
* {{deng|en|en-us-bowl.ogg|dever=DYA}}
* {{deng|en|LL-Q1860 (eng)-Nattes à chat-bowl.wav}}
* {{deng|en|LL-Q1860 (eng)-Samuel Muldoon (Muldoonsamuel55)-bowl.wav|dever=Aurora, DYA}}
=== Navdêr ===
{{en-navdêr}}
# [[kasik]], [[kase]] {{îzah|[[qab|qabeke]] [[xwarin|xwarinê]] yê bi [[şikl|şiklê]] [[nîv|nîv]]-[[kure]] yê ku [[ji bo]] [[xwarin|xwarinan]] [[girtin|bigire]] [[nav|nava]] [[xwe|xwe]], ji bo [[tevlihevkirin|tevlihevkirina]] xwarinan an ji bo [[servîskirin|servîskirina]] wan tê [[bikaranîn]], [[wekî|wekî]] [[nimûne|nimûne]] [[salate|salate]], [[fêkî|fêkî]] an [[şorbe|şorbe]] an [[tişt|tiştên]] [[din|din]] [[xistin|dixine]] [[navê|navê]]}}
# Bi [[miqdar|miqdara]] [[kase|kaseyekê]].
#: {{hevmane|en|bowlful}}
#: {{mk|en|You can’t have any more soup – you’ve had three '''bowls''' already.|Tu nikarî êdî zêdetir şorbe vexwî, te jixwe bi qasî sê kaseyan vexwariye.}}
# {{f|en|çêkirina xwarinê}} [[xwarin|Xwarineke]] ku ji [[têkilkirin|têkilkirina]] [[çend|çend]] [[xwarin|xwarinên]] [[cihê]] [[pêk hatin|pêk tê]], ku ne [[hewce|hewceyê]] [[temamî|temamiya]] wan were [[pijandin]], û di kaseyekê de tên [[servîs kirin|servîskirin]].
# [[tiraş|Tiraş]]eke [[por]]ê yê ku porên [[rast|rast]] ji [[kinar|kinara]] bi [[eynî]] [[dirêjahî|dirêjahiyê]] tên [[qusandin]] û şiklê kaseyekê [[derhatin|dertê]] holê.
#: {{hevmaneyên peyvê|en|bowl cut|pudding bowl}}
# [[qism|Qismê]] [[vala]] yê [[gilover]] yê her tiştekî.
#: {{mk|en|Direct the cleaning fluid around the toilet '''bowl''' and under the rim.|Aliyê şilaviya paqijkirinê bidin ber bi derdora qulika tiwaletê û binê kinarê.}}
## Qismê [[kevçî|kevçiyê]] yê ku tiştan [[hilgirtin|hildigire]], [[zidd|ziddê]] qismê [[destik|destikê]] (ku ti tiştî [[girtin|nagire]] nava xwe).
## Qismeke [[qelûn]]ê an [[nargîle]]yê ku ji bo [[kêşan|kêşanê]] bi [[marijuana]] ([[çeşîd|çeşîdeke]] [[esrar|esrarê]]) hatiye tijîkirin.
## {{f|en|tîpografî}} [[parçe|Parçeyek]] [[gilover|gilover]] a [[glîf|glîfê]] ku [[cih|cihê]] [[vala|vala]] [[peçandin|dipêçe]], wekî di [[herf|herfên]] "d" û "o"yê de.
# {{f|en|landforms}} [[krater|Krater]]eke gilover (an wekî wê) yê di [[zemîn|zemînê]] de.
#: {{hevmaneyên peyvê|en|crater|hollow}}
# {{f|en|spor|tiyatro}} [[stadyum|Stadyûm]] an [[amfîtiyatro|amfîtiyatroyeke]] bi şiklê [[elîpsê|elîpsê]] yê ku kaseyê tîna bîra mirov.
# {{f|en|futbola amerîkî}} [[pêşbazî|Pêşbaziyek]] [[futbol|futbolê]] yê [[piştî]] [[sezon|sezonê]] (wekî nimûne ''Rose Bowl'', ''Super Bowl'')
==== Jê ====
{{kol3|en
|begging bowl
|[[bowl cut]]
|bowl game
|bowl lute
|coffee bowl
|dustbowl
|fruit bowl
|mixing bowl
|pudding bowl
|salad bowl
|singing bowl
|soup bowl
|toilet bowl
|bowl barrow|bowl chair|bowl hat|bowl of cherries|bowl pack|bowl wax|Buddha bowl|clean bowl|doggy dinner bowl look|finger bowl|fish bowl|food bowl|goldfish bowl|iron rice bowl|lawn-bowl|mazer bowl|poké bowl|pudding-bowl|punch bowl|punch bowl waterfall|quiz bowl|rice bowl|scorpion bowl|shampoo bowl|shit bowl|shit-bowl|slop bowl|slop-bowl|spaghetti bowl|spoon bowl|spoon-bowl|sugar bowl|swill-bowl|take the browns to the super bowl|tea bowl|track and bowl system|volcano bowl|work bowl|Zuni bowl}}
==== Etîmolojî ====
Ji {{mîr|enm|en|bolle}}, ji {{mîr|ang|en|bolla}}, {{qal|ang|bolle||kase}}, ji {{mîr|gmw-pro|en|*bollā}}, ji {{mîr|gem-pro|en|*bullǭ||top, amana gilover}}.
Bide ber {{hv|frr|bol}}, {{hv|gml|bolle}}, {{qal|gml|bole||objeya gilover}}, {{hv|nl|bol||top, kure}}, {{hv|de|Bolle}}, {{hv|da|bolle}}, {{hv|is|bolli}}. {{cotreh|en|boule|pulla}}.
==== Dûnde ====
* {{dû|bi|bol}}
* {{dû|eo|bovlo|deyn=1}}
* {{dû|fr|bol|deyn=1}}
* {{dû|de|Bowle|Bowl|deyn=1}}
** {{dû|hu|bólé|deyn=1}}
* {{dû|io|bolo|deyn=1}}
* {{dû|ga|bolla|deyn=1}}
* {{dû|ja|ボール|tr=bōru|deyn=1}}
* {{dû|lv|bole|deyn=1}} {{mj|muhtemelen ji almanî}}
* {{dû|ko|볼|deyn=1}}
* {{dû|gd|bòl|deyn=1}}
* {{dû|sv|bål|deyn=1}}
=== Navdêr ===
{{en-navdêr}}
# [[gog|Gog]]a ku di lîstika ''lawn bowls''-ê de ji aliyê lîstikvanan ve tê gindirandin.
# {{f|en|di pirjimariyê de|lê bi lêkereke yekjimar tê bikaranîn}} Lîstikek e.
#: {{hevmaneyên peyvê|en|lawn bowls|lawn bowling}}
==== Jê ====
{{kol2|en|bowl-off|bowl-out}}
==== Etîmolojî ====
Ji {{mîr|enm|en|bowle}}, {{qal|enm|boule}}, ji {{ji|fro|en|boule||ball}}, ji {{ji|la|en|bulla||bubble, stud, round object}}. {{cotreh|en|poll}}.
=== Lêker ===
{{en-lêker}}
# {{f|en|gh}} Bi şikleke rast topê [[gindirandin]] an [[avêtin]], di lîstika krîketê de an di lîstik û sporên mîna wê de.
# {{f|en|ngh}} Ji bo avêtina topê (di krîketê de û di lîstik û sporên wekî wê de).
# {{f|en|ngh}} Lîstina bowlîngê an lîstikeke wek wê.
# Bi rehetî û hêsanî bar kirin an gindirandin, wekî ku li ser tekerekê be.
#: {{mk|en|We were '''bowled''' rapidly along the road.|Em bi lez û bez li ser rê gindirîbûn.}}
# Li tişekî dan ku digere an digindire.
==== Jê ====
{{kol3|en
|bowl a googly|
|bowl along
|bowler
|bowling
|bowling alley
|bowling ball
|bowl out
|bowl over
}}
==== Dûnde ====
* {{dû|da|bowle|deyn=1}}
* {{dû|nl|bowlen|deyn=1}}
* {{dû|de|bowlen|deyn=1}}
==== Anagram ====
* {{anagram|en|a=blow|Blow|b'low|blow}}
sft22f829eq076i85u2w9g0w5xzyy9t
6291571
6291570
2026-07-26T15:17:10Z
Wikihez
7344
/* */ bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re
6291571
wikitext
text/x-wiki
== {{ziman|en}} ==
[[File:Bowl MET DP243440 (cropped).jpg|thumb|{{zim|en|Ancient Egyptian faience bowl, from circa 200–150 BC}} Kaseyeke çînî ya Misra Kevnare, teqrîben ji salên 200–150 BM]]
[[File:Bowl (jatte à anses relevées or jatte écuelle) MET DP169254 (cropped).jpg|thumb|{{zim|en|French Neoclassical bowl (jatte à anses relevées or jatte écuelle), from circa 1787-1788}} Kaseyeke neoklasîk ya fransî (jatte à anses relevées or jatte écuelle), teqrîben ji salên 1787-1788]]
=== Bilêvkirin ===
* {{mj|KY}} {{IPA|en|/bəʊl/|[bɒʊɫ]}}
* {{mj|NZ|Awistralya}} {{IPA|en|/bɐʉl/|[bɒʊɫ]}}
* {{mj|DYA}} {{IPA|en|/boʊl/|[boɫ]}}
* {{deng|en|en-us-bowl.ogg|dever=DYA}}
* {{deng|en|LL-Q1860 (eng)-Nattes à chat-bowl.wav}}
* {{deng|en|LL-Q1860 (eng)-Samuel Muldoon (Muldoonsamuel55)-bowl.wav|dever=Aurora, DYA}}
=== Navdêr ===
{{en-navdêr}}
# [[kasik]], [[kase]] {{îzah|[[qab|qabeke]] [[xwarin|xwarinê]] yê bi [[şikl|şiklê]] [[nîv|nîv]]-[[kure]] yê ku [[ji bo]] [[xwarin|xwarinan]] [[girtin|bigire]] [[nav|nava]] [[xwe|xwe]], ji bo [[tevlihevkirin|tevlihevkirina]] xwarinan an ji bo [[servîskirin|servîskirina]] wan tê [[bikaranîn]], [[wekî|wekî]] [[nimûne|nimûne]] [[salate|salate]], [[fêkî|fêkî]] an [[şorbe|şorbe]] an [[tişt|tiştên]] [[din|din]] [[xistin|dixine]] [[navê|navê]]}}
# Bi [[miqdar|miqdara]] [[kase|kaseyekê]].
#: {{hevmane|en|bowlful}}
#: {{mk|en|You can’t have any more soup – you’ve had three '''bowls''' already.|Tu nikarî êdî zêdetir şorbe vexwî, te jixwe bi qasî sê kaseyan vexwariye.}}
# {{f|en|çêkirina xwarinê}} [[xwarin|Xwarineke]] ku ji [[têkilkirin|têkilkirina]] [[çend|çend]] [[xwarin|xwarinên]] [[cihê]] [[pêk hatin|pêk tê]], ku ne [[hewce|hewceyê]] [[temamî|temamiya]] wan were [[pijandin]], û di kaseyekê de tên [[servîs kirin|servîskirin]].
# [[tiraş|Tiraş]]eke [[por]]ê yê ku porên [[rast|rast]] ji [[kinar|kinara]] bi [[eynî]] [[dirêjahî|dirêjahiyê]] tên [[qusandin]] û şiklê kaseyekê [[derhatin|dertê]] holê.
#: {{hevmaneyên peyvê|en|bowl cut|pudding bowl}}
# [[qism|Qismê]] [[vala]] yê [[gilover]] yê her tiştekî.
#: {{mk|en|Direct the cleaning fluid around the toilet '''bowl''' and under the rim.|Aliyê şilaviya paqijkirinê bidin ber bi derdora qulika tiwaletê û binê kinarê.}}
## Qismê [[kevçî|kevçiyê]] yê ku tiştan [[hilgirtin|hildigire]], [[zidd|ziddê]] qismê [[destik|destikê]] (ku ti tiştî [[girtin|nagire]] nava xwe).
## Qismeke [[qelûn]]ê an [[nargîle]]yê ku ji bo [[kêşan|kêşanê]] bi [[marijuana]] ([[çeşîd|çeşîdeke]] [[esrar|esrarê]]) hatiye tijîkirin.
## {{f|en|tîpografî}} [[parçe|Parçeyek]] [[gilover|gilover]] a [[glîf|glîfê]] ku [[cih|cihê]] [[vala|vala]] [[peçandin|dipêçe]], wekî di [[herf|herfên]] "d" û "o"yê de.
# {{f|en|landforms}} [[krater|Krater]]eke gilover (an wekî wê) yê di [[zemîn|zemînê]] de.
#: {{hevmaneyên peyvê|en|crater|hollow}}
# {{f|en|spor|tiyatro}} [[stadyum|Stadyûm]] an [[amfîtiyatro|amfîtiyatroyeke]] bi şiklê [[elîpsê|elîpsê]] yê ku kaseyê tîna bîra mirov.
# {{f|en|futbola amerîkî}} [[pêşbazî|Pêşbaziyek]] [[futbol|futbolê]] yê [[piştî]] [[sezon|sezonê]] (wekî nimûne ''Rose Bowl'', ''Super Bowl'')
==== Jê ====
{{kol3|en
|begging bowl
|[[bowl cut]]
|bowl game
|bowl lute
|coffee bowl
|dustbowl
|fruit bowl
|mixing bowl
|pudding bowl
|salad bowl
|singing bowl
|soup bowl
|toilet bowl
|bowl barrow|bowl chair|bowl hat|bowl of cherries|bowl pack|bowl wax|Buddha bowl|clean bowl|doggy dinner bowl look|finger bowl|fish bowl|food bowl|goldfish bowl|iron rice bowl|lawn-bowl|mazer bowl|poké bowl|pudding-bowl|punch bowl|punch bowl waterfall|quiz bowl|rice bowl|scorpion bowl|shampoo bowl|shit bowl|shit-bowl|slop bowl|slop-bowl|spaghetti bowl|spoon bowl|spoon-bowl|sugar bowl|swill-bowl|take the browns to the super bowl|tea bowl|track and bowl system|volcano bowl|work bowl|Zuni bowl}}
==== Etîmolojî ====
Ji {{mîr|enm|en|bolle}}, ji {{mîr|ang|en|bolla}}, {{qal|ang|bolle||kase}}, ji {{mîr|gmw-pro|en|*bollā}}, ji {{mîr|gem-pro|en|*bullǭ||top, amana gilover}}.
Bide ber {{hv|frr|bol}}, {{hv|gml|bolle}}, {{qal|gml|bole||objeya gilover}}, {{hv|nl|bol||top, kure}}, {{hv|de|Bolle}}, {{hv|da|bolle}}, {{hv|is|bolli}}. {{cotreh|en|boule|pulla}}.
==== Dûnde ====
* {{dû|bi|bol}}
* {{dû|eo|bovlo|deyn=1}}
* {{dû|fr|bol|deyn=1}}
* {{dû|de|Bowle|Bowl|deyn=1}}
** {{dû|hu|bólé|deyn=1}}
* {{dû|io|bolo|deyn=1}}
* {{dû|ga|bolla|deyn=1}}
* {{dû|ja|ボール|tr=bōru|deyn=1}}
* {{dû|lv|bole|deyn=1}} {{mj|muhtemelen ji almanî}}
* {{dû|ko|볼|deyn=1}}
* {{dû|gd|bòl|deyn=1}}
* {{dû|sv|bål|deyn=1}}
=== Navdêr ===
{{en-navdêr}}
# [[gog|Gog]]a ku di lîstika ''lawn bowls''-ê de ji aliyê lîstikvanan ve tê gindirandin.
# {{f|en|di pirjimariyê de|lê bi lêkereke yekjimar tê bikaranîn}} Lîstikek e.
#: {{hevmaneyên peyvê|en|lawn bowls|lawn bowling}}
==== Jê ====
{{kol2|en|bowl-off|bowl-out}}
==== Etîmolojî ====
Ji {{mîr|enm|en|bowle}}, {{qal|enm|boule}}, ji {{ji|fro|en|boule||ball}}, ji {{ji|la|en|bulla||bubble, stud, round object}}. {{cotreh|en|poll}}.
=== Lêker ===
{{en-lêker}}
# {{f|en|gh}} Bi şikleke rast topê [[gindirandin]] an [[avêtin]], di lîstika krîketê de an di lîstik û sporên mîna wê de.
# {{f|en|ngh}} Ji bo avêtina topê (di krîketê de û di lîstik û sporên wekî wê de).
# {{f|en|ngh}} Lîstina bowlîngê an lîstikeke wek wê.
# Bi rehetî û hêsanî bar kirin an gindirandin, wekî ku li ser tekerekê be.
#: {{mk|en|We were '''bowled''' rapidly along the road.|Em bi lez û bez li ser rê gindirîbûn.}}
# Li tişekî dan ku digere an digindire.
==== Jê ====
{{kol3|en
|bowl a googly|
|bowl along
|bowler
|bowling
|bowling alley
|bowling ball
|bowl out
|bowl over
}}
==== Dûnde ====
* {{dû|da|bowle|deyn=1}}
* {{dû|nl|bowlen|deyn=1}}
* {{dû|de|bowlen|deyn=1}}
==== Anagram ====
* {{anagram|en|a=blow|Blow|b'low|blow}}
pj6qg4f64f3m7sxf3joicku7a8y6kcn
Şablon:bêçavkanî
10
448836
6291615
6117825
2026-07-26T22:53:12Z
Wikihez
7344
6291615
wikitext
text/x-wiki
{{hişyarî
| rengê hundir = var(--wikt-palette-white, white)
| rengê sîndorî = var(--wikt-palette-red-8, #C00)
| rengê sîndorê çep = var(--wikt-palette-red-8, #C00)
| wêne = Question_book-new.svg
| nivîs = {{reng2|var(--wikt-palette-red-8, #C00)|'''Çavkaniya vê madeyê tine ye û ji ber wê peyitandina malûmatan ne mimkin e.'''}}
| nivîsa jêr = <small>Ji bo başkirina vê madeyê ji kerema xwe re çavkaniyan pê ve bike. Herwiha binêre gotûbêja li [[Wîkîferheng:Daxwazên peyitandinê#{{FULLPAGENAME}}|Wîkîferheng:Daxwazên peyitandinê]].<sup class="plainlinks" style="font-weight:normal" title="Li Daxwazên peyitandinê zêde bike"> (<!--
-->[{{fullurle:Wîkîferheng:Daxwazên peyitandinê|action=edit§ion=new&preload=Şablon:p-wate/preload&preloadtitle=%5B%5B{{urlencode:{{FULLPAGENAME}}}}}}%5D%5D +]<!--
-->)</sup></small><br/>{{çavkanî bigere}}
}}{{#tag:indicator|[[Wêne:Question_book-new.svg|25px|Gotarên bêçavkanî|link={{#if:{{{ziman|}}}|{{PAGENAME}}#{{ucfirst:{{nav ziman|{{{ziman|}}}}}}}|Kategorî:Madeyên bêçavkanî}}]]|name=Bêçavkanî}}<includeonly>[[Kategorî:Madeyên bêçavkanî]]</includeonly><noinclude>
{{belgekirin}}
</noinclude>
j6xviz6qyfpc2t2y4e3l0u8kupr80q3
Şablon:hişyarî
10
517656
6291614
6235346
2026-07-26T22:52:48Z
Wikihez
7344
6291614
wikitext
text/x-wiki
{| align="{{{align|center}}}" width="80%" id="{{{id|}}}" style="padding:0em; margin-top:0.5em; margin-bottom:0.5em; border-width:1px 1px 1px 5px; border-style: solid; border-color: {{{rengê sîndorî|var(--wikt-palette-lightergrey, #EEE9E1)}}} {{{rengê sîndorê rast|{{{rengê sîndorî|var(--wikt-palette-lightergrey, #EEE9E1)}}}}}} {{{rengê sîndorî|var(--wikt-palette-lightergrey, #EEE9E1)}}} {{{rengê sîndorê çep|var(--wikt-palette-grey, grey)}}}; background:{{{rengê hundir|transparent}}}; color: var(--wikt-palette-black, #202122); line-height:1.4em;{{{style|}}}"
|-
{{#if:{{{wêne|}}} |
{{!}} style="padding:0em 0.3em 0em 0.8em" {{!}} [[Wêne:{{{wêne}}}|{{{wênefirehî|45px}}}|link={{{link|}}}]]
}}
|style="padding-right:0.5em; padding-left:0.5em" | {{{nivîs}}}{{#if:{{{nivîsa jêr|}}}|<br />{{{nivîsa jêr|}}}|}}
|}{{clr}}<noinclude>{{belgekirin}}</noinclude>
ndmusqzu6xblpbhhkcq8q9mdxez7l3h
Modul:dara kategoriyan
828
876148
6291616
6114179
2026-07-26T22:56:26Z
Wikihez
7344
6291616
Scribunto
text/plain
local export = {}
local m_utilities = require('Modul:utilities')
local inFundamental = mw.loadData("Modul:dara kategoriyan/dane")
local show_error, check_name, link_box, show_catfix, show_categories,
show_editlink, show_pagelist, show_breadcrumbs, show_description,
show_TOC
local function capitalize(text)
return mw.getContentLanguage():ucfirst(text)
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local template = frame.args["template"]
if not template or template == "" then
error("Parametreya \"template\" nehatiye dayîn.")
end
if mw.title.getCurrentTitle().nsText == "Şablon" then
local text = {}
table.insert(text, "[[Kategorî:Şablonên kategorîkirinê|*]]Ev şablon tenê di rûpelên Kategorî: de tê bikaranîn.")
table.insert(text, " Ji bo agahiyên zêdetir binêre [[Modul:dara kategoriyan]] û binmodulên wê [[Modul:dara kategoriyan/")
table.insert(text, template .. "]].")
if frame.args["useautocat"] then
table.insert(text, " Dewsa bikaranîna vê şablonê şablona ")
table.insert(text, require("Modul:template link").format_link({"katbike"}))
table.insert(text, " (bêyî nivîsîna tiştekî din), bi kar bîne.")
end
return table.concat(text)
elseif mw.title.getCurrentTitle().nsText ~= "Kategorî" then
error("Ev şablon tenê di rûpelên bi Kategorî: dest pê dikin de tê bikaranîn.")
end
local submodule = require("Modul:dara kategoriyan/" .. template)
-- Get all the parameters and the label data
local current
if submodule.new_main then
current = submodule.new_main(frame)
else
local info = {}
for key, val in pairs(frame.args) do
if val ~= "" and key ~= "useautocat" then
info[key] = val
end
end
info.template = nil
current = submodule.new(info, true)
end
local functions = {
"getBreadcrumbName",
"getDataModule",
"canBeEmpty",
"getDescription",
"getParents",
"getUmbrella",
}
if current then
for i, functionName in pairs(functions) do
if type(current[functionName]) ~= "function" then
require("Modul:debug").track{ "dara kategoriyan/kêmasiya fonksîyonê", "dara kategoriyan/kêmasiya fonksîyonê/" .. functionName }
end
end
end
local boxes = {}
local display = {}
local categories = {}
if template == "topic cat" then
table.insert(categories, "[[Kategorî:topic cat]]")
end
-- Check if the category is empty
local isEmpty = mw.site.stats.pagesInCategory(mw.title.getCurrentTitle().text, "all") == 0
-- Are the parameters valid?
if not current then
table.insert(categories, "[[Kategorî:Kategoriyên bi etîketa xelet]]")
table.insert(categories, isEmpty and "[[Kategorî:Kategoriyên vala]]" or nil)
table.insert(display, show_error(
"Etîketa di şablona " ..
require("Modul:template link").format_link{template} ..
" de naxebite. Dibe ku xelet be yan hîn nehatibe çêkirin. " ..
"Ji bo lêzêdekirina etîketa nû ji kerema xwe binêre belgekirina şablonê."))
-- Exit here, as all code beyond here relies on current not being nil
return table.concat(categories, "") .. table.concat(display, "\n\n")
end
-- Does the category have the correct name?
if mw.title.getCurrentTitle().text ~= current:getCategoryName() then
table.insert(categories, "[[Kategorî:Kategoriyên bi navê xelet]]")
table.insert(display, show_error(
"Li gorî parametreyên şablona " ..
require("Modul:template link").format_link{template} ..
", navê vê kategoriyê divê '''[[:Kategorî:" .. current:getCategoryName() .. "]]''' be."))
end
-- Add cleanup category for empty categories
local canBeEmpty = current:canBeEmpty()
if isEmpty and not canBeEmpty then
table.insert(categories, "[[Kategorî:Kategoriyên vala]]")
end
if current:isHidden() then
table.insert(categories, "__HIDDENCAT__")
end
if canBeEmpty then
table.insert(categories, " __EXPECTUNUSEDCATEGORY__")
end
table.insert(boxes, show_editlink(current))
-- Generate the displayed information
table.insert(display, show_breadcrumbs(current))
table.insert(display, show_description(current))
table.insert(display, show_TOC(current))
table.insert(display, show_catfix(current))
show_categories(current, categories)
return table.concat(boxes, "\n") .. "\n" .. table.concat(display, "\n\n") .. table.concat(categories, "")
end
function show_error(text)
return mw.getCurrentFrame():expandTemplate{title = "maintenance box", args = {
"red",
image = "[[File:Ambox warning pn.svg|50px]]",
title = "Xeletî di vê kategoriyê de hene.",
text = text,
}}
end
-- Check the name of the current page, and return an error if it's not right.
function check_name(current, template, info)
local errortext = nil
local category = nil
if not current then
errortext =
"Etîketa \"" .. (info.label or "") .. "\" di şablona " ..
require("Modul:template link").format_link{template} .. " de naxebite. " ..
"Dibe ku xelet be yan hîn nehatibe çêkirin. Ji bo lêzêdekirina etîketa nû ji kerema xwe binêre belgekirina şablonê."
category = "[[Kategorî:Kategoriyên bi etîketa xelet]]"
else
end
if errortext then
return (category or "") .. show_error(errortext)
else
return nil
end
end
function show_catfix(current)
local skip_etiket = {
["werger"] = true,
["daxwazên peyvan"] = true,
["daxwazên skrîpta peyvê"] = true,
["transliterasyona nepêwîst"] = true,
}
local etiket = current:getInfo().label
if skip_etiket[etiket] then
return nil
end
if current._lang and not (current._info and current._info.no_catfix) then
-- Use pcall to safely execute the potentially problematic code
local success, result = pcall(function()
local script = nil
if current._info and current._info.sc then
script = require("Modul:scripts").getByCode(current._info.sc)
end
return m_utilities.catfix(current._lang, script)
end)
-- If successful, return the result, otherwise return nil
if success then
return result
else
return nil -- Silently ignore any errors
end
else
return nil
end
end
-- Show the parent categories that the current category should be placed in.
function show_categories(current, categories)
local parents = current:getParents()
if not parents then
return
end
for _, parent in ipairs(parents) do
if type(parent.name) == "string" then
if not (current._lang and current:getCategoryName() == capitalize(current._lang:getCategoryName())) and not (current._sc and current:getCategoryName():find(capitalize(current._sc:getCategoryName()), nil, true)) and current:getInfo().code then
require("Modul:debug").track("category tree/string")
end
table.insert(categories, "[[" .. parent.name .. "|" .. parent.sort .. "]]")
else
table.insert(categories, "[[Kategorî:" .. parent.name:getCategoryName() .. "|" .. parent.sort .. "]]")
end
end
-- Also put the category in its corresponding "umbrella" or "by language" category.
local umbrella = current:getUmbrella()
local naveModule = current:getDataModule()
if umbrella then
local sort
if current._lang then
sort = current._lang:getCanonicalName()
elseif naveModule == "Modul:dara kategoriyan/malbatkatbike" then
sort = mw.ustring.gsub(current:getCategoryName(), "Zimanên ", "")
else
sort = current:getCategoryName()
end
if type(umbrella) == "string" then
table.insert(categories, "[[" .. umbrella .. "|" .. sort .. "]]")
else
table.insert(categories, "[[Kategorî:" .. umbrella:getCategoryName() .. "|" .. sort .. "]]")
end
end
end
function link_box(content)
return "<div class=\"tablo-flip noprint plainlinks nomobile\" style=\"margin: 0 0 .5em 1em; background: var(--wikt-palette-paleblue, #f9f9f9); border: 1px var(--wikt-palette-grey, #aaaaaa) solid; margin-top: -1px; padding: 5px; font-weight: bold; color: var(--wikt-palette-black, #202122);\">"
.. content .. "</div>"
end
function show_editlink(current)
return link_box(
"[" .. tostring(mw.uri.fullUrl(current:getDataModule(), "action=edit"))
.. " Daneya kategoriyê biguhêre]")
end
-- Show navigational "breadcrumbs" at the top of the page.
function show_breadcrumbs(current)
local steps = {}
-- Start at the current label and move our way up the "chain" from child to parent, until we can't go further.
while current do
local category = nil
local display_name = nil
if type(current) == "string" then
category = current
display_name = current:gsub("^Kategorî:", "")
else
category = "Kategorî:" .. current:getCategoryName()
display_name = current:getBreadcrumbName()
end
display_name = capitalize(display_name)
table.insert(steps, 1, "[[:" .. category .. "|" .. display_name .. "]]")
-- Move up the "chain" by one level.
if type(current) == "string" then
current = nil
else
current = current:getParents()
end
if current then
current = current[1].name
elseif inFundamental[category] then ---fundamental li ser Wîkîferhengê !Serkategorî ye
current = "Kategorî:!Serkategorî"
end
end
steps = table.concat(steps, " » ")
return "<small>" .. steps .. "</small>"
end
-- Show a short description text for the category.
function show_description(current)
return (current:getDescription() or "")
end
-- Show a table of contents with links to each letter in the language's script.
function show_TOC(current)
local code = current:getInfo().code
if code and not require("Modul:languages").getByCode(code) then
return nil
end
if not code then
code = "ku"
end
local etiket = current:getInfo().label
if etiket == "werger" or etiket == "daxwazên peyvan" then
code = "ku"
end
-- If category can be empty, then it only contains subcategories.
local hasPages = not current:canBeEmpty()
local titleText = mw.title.getCurrentTitle().text
local inCategory
if hasPages then
inCategory = mw.site.stats.pagesInCategory(titleText, "pages")
else
inCategory = mw.site.stats.pagesInCategory(titleText, "subcats")
end
-- No need for a TOC if all pages or subcategories can fit on one page.
if inCategory > 200 then
-- This category is very large, see if there is an "extended" version of the TOC.
if inCategory > 1000 then
local TOC_template_extended = mw.title.new("Şablon:" .. code .. "-KategorîTOC dirêj")
local TOC_template = mw.title.new("Şablon:".. code .. "-KategorîTOC" )
if TOC_template_extended.exists then
return mw.getCurrentFrame():expandTemplate{title = TOC_template_extended.text, args = {}}
elseif TOC_template.exists then
return mw.getCurrentFrame():expandTemplate{title = TOC_template.text, args = {}}
else
return "[[Kategorî:Naverok hewce ye|" .. code .. "]]"
end
end
local TOC_template = mw.title.new("Şablon:".. code .. "-KategorîTOC" )
if TOC_template.exists then
return mw.getCurrentFrame():expandTemplate{title = TOC_template.text, args = {}}
else
return "[[Kategorî:Naverok hewce ye|" .. code .. "]]"
end
end
return nil
end
function export.test(frame)
local template = frame.args[1]
local submodule = require("Modul:dara kategoriyan/" .. template)
if submodule.new_main then
current = submodule.new_main(frame)
else
local info = {}
for key, val in pairs(frame.args) do
info[key] = val; if info[key] == "" then info[key] = nil end
end
info.template = nil
current = submodule.new(info, true)
end
end
return export
599s52cgjdlz7nsuvybcko5q902103c
Modul:dara kategoriyan/zimankatbike
828
876149
6291617
6113772
2026-07-26T23:01:58Z
Wikihez
7344
6291617
Scribunto
text/plain
local export = {}
local new_title = mw.title.new
local ucfirst = require("Module:string utilities").ucfirst
local split = require("Module:string utilities").split
local m_languages = require("Module:languages")
local m_sc_getByCode = require("Module:scripts").getByCode
local m_table = require("Module:table")
local parse_utilities_module = "Module:parse utilities"
local concat = table.concat
local insert = table.insert
local reverse_ipairs = m_table.reverseIpairs
local serial_comma_join = m_table.serialCommaJoin
local size = m_table.size
local sorted_pairs = m_table.sortedPairs
local to_json = require("Module:JSON").toJSON
local Hang = m_sc_getByCode("Hang")
local Hani = m_sc_getByCode("Hani")
local Hira = m_sc_getByCode("Hira")
local Hrkt = m_sc_getByCode("Hrkt")
local Kana = m_sc_getByCode("Kana")
local function makeCategoryLink(object)
return "[[:Kategorî:" .. object:getCategoryName() .. "|" .. object:getCanonicalName() .. "]]"
end
local function linkbox(lang, setwiki, setwikt, setsister, entryname)
local wiktionarylinks = "''Tine.''"
local canonicalName = lang:getCanonicalName()
local wikimediaLanguages = lang:getWikimediaLanguages()
local categoryName = lang:getCategoryName()
local categoryName = mw.ustring.lower( categoryName )
local wikipediaArticle = setwiki or lang:getWikipediaArticle()
if setwikt then
require "Modul:debug".track "langcatboiler/setwikt"
if setwikt == "-" then
require "Modul:debug".track "langcatboiler/setwikt/hyphen"
end
end
if setwikt ~= "-" and wikimediaLanguages and wikimediaLanguages[1] then
wiktionarylinks = {}
for _, wikimedialang in ipairs(wikimediaLanguages) do
table.insert(wiktionarylinks,
(wikimedialang:getCanonicalName() ~= canonicalName and "(''" .. wikimedialang:getCanonicalName() .. "'') " or "") ..
"'''[[:" .. wikimedialang:getCode() .. ":|" .. wikimedialang:getCode() .. ".wiktionary.org]]'''")
end
wiktionarylinks = table.concat(wiktionarylinks, "<br/>")
end
local plural = wikimediaLanguages[2] and "s" or ""
return table.concat{
[=[<div class="tablo-flip" style="border: solid var(--wikt-palette-grey, #aaa) 1px; margin: 1 1 1 1; background: var(--wikt-palette-paleblue, #f9f9f9); width: 270px; padding: 5px; margin: 5px;">
<div style="text-align: center; margin-bottom: 10px; margin-top: 5px">'''Zimanê ]=], categoryName, [=['''</div>
{| style="font-size: 90%; background: var(--wikt-palette-paleblue, #f9f9f9);"
|-
| style="vertical-align: middle; height: 35px; width: 35px;" | [[File:Wiktionary-logo-v2.svg|35px|none|Wiktionary]]
|| ''Çapa ]=], categoryName, [=[ ya Wîkîferhengê''
|-
| colspan="2" style="padding-left: 10px; border-bottom: 1px solid var(--wikt-palette-lightgrey, lightgray);" | ]=], wiktionarylinks, [=[
|-
| style="vertical-align: middle; height: 35px" | [[File:Wikipedia-logo.png|35px|none|Wikipedia]]
|| ''Meqaleya Wîkîpediyê ya li ser ]=], categoryName, [=[''
|-
| colspan="2" style="padding-left: 10px; border-bottom: 1px solid var(--wikt-palette-lightgrey, lightgray);" | ]=], (setwiki == "-" and "''Tine.''" or "'''[[w:" .. wikipediaArticle .. "|" .. wikipediaArticle .. "]]'''"), [=[
|-
| style="vertical-align: middle; height: 35px" | [[File:Open book nae 02.svg|35px|none|Made]]
|| Madeya Wîkîferhengê ya li ser ]=], categoryName,[=[
|-
| colspan="2" style="padding-left: 10px;" | ''']=], require("Modul:links").full_link({lang = require("Modul:languages").getByCode("ku"), term = categoryName}), [=['''
|}
</div>]=]
}
end
local function edit_link(title, text)
return '<span class="plainlinks">['
.. tostring(mw.uri.fullUrl(title, { action = "edit" }))
.. ' ' .. text .. ']</span>'
end
-- Should perhaps use wiki syntax.
local function infobox(lang)
local ret = {}
insert(ret, '<table class="wikitable language-category-info"')
local raw_data = lang:getData("extra")
if raw_data then
local replacements = {
[1] = "canonical-name",
[2] = "wikidata-item",
[3] = "family",
[4] = "scripts",
}
local function replacer(letter1, letter2)
return letter1:lower() .. "-" .. letter2:lower()
end
-- For each key in the language data modules, returns a descriptive
-- kebab-case version (containing ASCII lowercase words separated
-- by hyphens).
local function kebab_case(key)
key = replacements[key] or key
key = key:gsub("(%l)(%u)", replacer):gsub("(%l)_(%l)", replacer)
return key
end
local compress = {compress = true}
local function html_attribute_encode(str)
str = to_json(str, compress)
:gsub('"', """)
-- & in attributes is automatically escaped.
-- :gsub("&", "&")
:gsub("<", "<")
:gsub(">", ">")
return str
end
insert(ret, ' data-code="' .. lang:getCode() .. '"')
for k, v in sorted_pairs(raw_data) do
insert(ret, " data-" .. kebab_case(k)
.. '="'
.. html_attribute_encode(v)
.. '"')
end
end
insert(ret, '>\n')
insert(ret, '<tr class="language-category-data">\n<th colspan="2">'
.. edit_link(lang:getDataModuleName(), "Datayên vî zimanî biguhêre")
.. "</th>\n</tr>\n")
insert(ret, "<tr>\n<th>Navê zimanî</th><td>" .. ucfirst(lang:getCanonicalName()) .. "</td>\n</tr>\n")
local otherNames = lang:getOtherNames()
if otherNames then
local names = {}
for _, name in ipairs(otherNames) do
insert(names, "<li>" .. ucfirst(name) .. "</li>")
end
if #names > 0 then
insert(ret, "<tr>\n<th>Navên din</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
local aliases = lang:getAliases()
if aliases then
local names = {}
for _, name in ipairs(aliases) do
insert(names, "<li>" ..ucfirst(name) .. "</li>")
end
if #names > 0 then
insert(ret, "<tr>\n<th>Bernav</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
local varieties = lang:getVarieties()
if varieties then
local names = {}
for _, name in ipairs(varieties) do
if type(name) == "string" then
insert(names, "<li>" .. ucfirst(name) .. "</li>")
else
assert(type(name) == "table")
local first_var
local subvars = {}
for i, var in ipairs(name) do
if i == 1 then
first_var = var
else
insert(subvars, "<li>" .. ucfirst(var) .. "</li>")
end
end
if #subvars > 0 then
insert(names, "<li><dl><dt>" .. first_var .. "</dt>\n<dd><ul>" .. concat(subvars, "\n") .. "</ul></dd></dl></li>")
elseif first_var then
insert(names, "<li>" .. first_var .. "</li>")
end
end
end
if #names > 0 then
insert(ret, "<tr>\n<th>Varyant</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
insert(ret, "<tr>\n<th>[[WF:LZ|Koda zimanî]]</th><td><code>" .. lang:getCode() .. "</code></td>\n</tr>\n")
insert(ret, "<tr>\n<th>[[WF:Lîsteya malbatan|Malbata zimanî]]</th>\n")
local fam = lang:getFamily()
local famCode = fam and fam:getCode()
if not fam then
insert(ret, "<td>nehatiye sinifandin</td>")
elseif famCode == "qfa-iso" then
insert(ret, "<td>[[:Kategorî:Zimanên îzole|Zimanekî îzole]]</td>")
elseif famCode == "qfa-mix" then
insert(ret, "<td>[[:Kategorî:Zimanên têkilkirî|Zimanekî têkilkirî]]</td>")
elseif famCode == "sgn" then
insert(ret, "<td>[[:Kategorî:Zimanên destan|Zimanê destan]]</td>")
elseif famCode == "crp" then
insert(ret, "<td>[[:Kategorî:Zimanên kreyolî yan pîdgînî|Kreyolî yan pîdgînî]]</td>")
elseif famCode == "art" then
insert(ret, "<td>[[:Kategorî:Zimanên çêkirî|Zimanekî çêkirî]]</td>")
else
insert(ret, "<td>" .. ucfirst(fam:makeCategoryLink()) .. "</td>")
end
insert(ret, "\n</tr>\n<tr>\n<th>Zimanên pêşî</th>\n<td>")
local ancestors = lang:getAncestors()
if ancestors[2] then
local ancestorList = {}
for i, anc in ipairs(ancestors) do
ancestorList[i] = "<li>" .. anc:makeCategoryLink() .. "</li>"
end
insert(ret, "<ul>\n" .. concat(ancestorList, "\n") .. "</ul>")
else
local ancestorChain = lang:getAncestorChainOld()
if ancestorChain[1] then
local chain = {}
for _, anc in reverse_ipairs(ancestorChain) do
insert(chain, "<li>" .. anc:makeCategoryLink() .. "</li>")
end
insert(ret, "<ul>\n" .. concat(chain, "\n<ul>\n") .. ("</ul>"):rep(#chain))
else
insert(ret, "unknown")
end
end
insert(ret, "</td>\n</tr>\n")
local scripts = lang:getScripts()
if scripts[1] then
local script_text = {}
local function makeScriptLine(sc)
local code = sc:getCode()
local url = tostring(mw.uri.fullUrl('Special:Search', {
search = 'contentmodel:css insource:"' .. code
.. '" insource:/\\.' .. code .. '/',
ns8 = '1'
}))
return sc:getCanonicalName()
.. ' (<span class="plainlinks" title="Search for stylesheets referencing this script">[' .. url .. ' <code>' .. code .. '</code>]</span>)'
end
local function add_Hrkt(text)
insert(text, "<li>" .. makeScriptLine(Hrkt))
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hira) .. "</li>")
insert(text, "<li>" .. makeScriptLine(Kana) .. "</li>")
insert(text, "</ul>")
insert(text, "</li>")
end
for _, sc in ipairs(scripts) do
local text = {}
local code = sc:getCode()
if code == "Hrkt" then
add_Hrkt(text)
else
insert(text, "<li>" .. makeScriptLine(sc))
if code == "Jpan" then
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hani) .. "</li>")
add_Hrkt(text)
insert(text, "</ul>")
elseif code == "Kore" then
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hang) .. "</li>")
insert(text, "<li>" .. makeScriptLine(Hani) .. "</li>")
insert(text, "</ul>")
end
insert(text, "</li>")
end
insert(script_text, concat(text, "\n"))
end
insert(ret, "<tr>\n<th>[[Modul:scripts/data|Skrîpt]]</th>\n<td><ul>\n" .. concat(script_text, "\n") .. "</ul></td>\n</tr>\n")
else
insert(ret, "<tr>\n<th>[[Modul:scripts/data|Skrîpt]]</th>\n<td>nehatiye nivîsin</td>\n</tr>\n")
end
local function add_module_info(raw_data, heading)
if raw_data then
local scripts = lang:getScriptCodes()
local module_info, add = {}, false
if type(raw_data) == "string" then
insert(module_info,
("[[Module:%s]]"):format(raw_data))
add = true
else
local raw_data_type = type(raw_data)
if raw_data_type == "table" and size(scripts) == 1 and type(raw_data[scripts[1]]) == "string" then
insert(module_info,
("[[Module:%s]]"):format(raw_data[scripts[1]]))
add = true
elseif raw_data_type == "table" then
insert(module_info, "<ul>")
for script, data in sorted_pairs(raw_data) do
if type(data) == "string" and m_sc_getByCode(script) then
insert(module_info, ("<li><code>%s</code>: [[Module:%s]]</li>"):format(script, data))
end
end
insert(module_info, "</ul>")
add = size(module_info) > 2
end
end
if add then
insert(ret, [=[
<tr>
<th>]=] .. heading .. [=[</th>
<td>]=] .. concat(module_info) .. [=[</td>
</tr>
]=])
end
end
end
add_module_info(raw_data.generate_forms, "Form-generating<br>module")
add_module_info(raw_data.translit, "Modula<br>translîterasyonê")
add_module_info(raw_data.display_text, "Display text<br>module")
add_module_info(raw_data.entry_name, "Entry name<br>module")
add_module_info(raw_data.sort_key, "Modula<br>rêzkirinê")
local wikidataItem = lang:getWikidataItem()
if lang:getWikidataItem() and mw.wikibase then
local URL = mw.wikibase.getEntityUrl(wikidataItem)
local link
if URL then
link = '[' .. URL .. ' ' .. wikidataItem .. ']'
else
link = '<span class="error">Invalid Wikidata item: <code>' .. wikidataItem .. '</code></span>'
end
insert(ret, "<tr><th>Wîkîdata</th><td>" .. link .. "</td></tr>")
end
insert(ret, "</table>")
return concat(ret)
end
local function NavFrame(content, title)
return '<div class="NavFrame"><div class="NavHead">'
.. (title or '{{{title}}}') .. '</div>'
.. '<div class="NavContent" style="text-align: left;">'
.. content
.. '</div></div>'
end
function export.country_categories(frame)
end
-- Category object
local Category = {}
Category.__index = Category
function Category.new(info)
for key, val in pairs(info) do
if not (key == "code" or key == "entryname" or key == "setsister" or key == "setwiki" or key == "setwikt") then
error("The parameter \"" .. key .. "\" was not recognized.")
end
end
local self = setmetatable({}, Category)
self._info = info
if not self._info.code then
error("Koda zimanî nehatiye nivîsîn.")
else
self._lang = require("Modul:languages").getByCode(self._info.code) or error("Koda zimanî \"" .. self._info.code .. "\" ne guncav e.")
end
return self
end
export.new = Category.new
function Category:getInfo()
return self._info
end
function Category:getBreadcrumbName()
return self._lang:getCanonicalName()
end
function Category:getDataModule()
return "Modul:dara kategoriyan/zimankatbike"
end
function Category:canBeEmpty()
return false
end
function Category:isHidden()
return false
end
function Category:getCategoryName()
return mw.getContentLanguage():ucfirst(self._lang:getCategoryName())
end
function Category:getDescription()
if self._lang:getCode() == "und" then
return
"Kategoriya sereke ya '''" .. self._lang:getCategoryName() .. "''', represented in Wiktionary by the [[Wiktionary:Languages|code]] '''" .. self._lang:getCode() .. "'''. " ..
"This language contains terms in historical writing, whose meaning has not yet been determined by scholars."
end
local canonicalName = self._lang:getCanonicalName()
local ret = linkbox(self._lang, self._info.setwiki, self._info.setwikt, self._info.setsister, self._info.entryname)
ret = ret .. "Ev ji bo zimanê '''" .. mw.ustring.lower(self._lang:getCategoryName()) .. "''' kategoriya sereke ye.\n\nDanasîna zimanî:\n\n" .. infobox(self._lang)
if self._lang:hasType("reconstructed") then
ret = ret .. "\n\n" ..
mw.getContentLanguage():ucfirst(canonicalName) .. " zimanekî ji nû ve sazkirî ye. Lewra peyvên di vê rûpelê de yekser nehatine peyitandin." ..
" Lê belê li gorî rêbaza berawirdkirinê piştraskirî ne û tên bawerkirin ku di wextekî de hebûn.\n\n" ..
"Peyvên bi zimanê " .. canonicalName ..
" nabe ku cihê xwe valahiya nav ya sereke de bigrin, van peyvan divê bi [[Wîkîferheng:Jinûvesazî|'''Jinûvesazî:''']] dest pê bikin."
-- elseif self._lang:hasType("appendix-constructed") then
-- ret = ret .. "\n\n" ..
-- ucfirst(canonicalName) .. " zimanekî çêkirî ye. Loma " ..
-- "divê madeyên bi " .. canonicalName ..
-- " di valahiya nav a Pêvek: de werin qeydkirin. " ..
-- "Ji bo hemû madeyan binêre [[Pêvek:" .. ucfirst(canonicalName) .. "]]."
end
ret = ret .. " __EXPECTUNUSEDCATEGORY__" -- it's OK if this category is empty
return ret
end
function Category:getParents()
local canonicalName = self._lang:getCanonicalName()
local ret = {{name = "Kategorî:Hemû ziman", sort = canonicalName}}
local fam = self._lang:getFamily()
local famCode = fam and fam:getCode()
if not fam then
table.insert(ret, {name = "Kategorî:Zimanên ku nehatine sinifandin", sort = canonicalName})
elseif famCode == "qfa-iso" then
table.insert(ret, {name = "Kategorî:Zimanên îzole", sort = canonicalName})
elseif famCode == "qfa-mix" then
table.insert(ret, {name = "Kategorî:Zimanên têkilkirî", sort = canonicalName})
elseif famCode == "sgn" then
table.insert(ret, {name = "Kategorî:Zimanên destan", sort = canonicalName})
elseif famCode == "crp" then
table.insert(ret, {name = "Kategorî:Zimanên kreyolî yan pîdgînî", sort = canonicalName})
for _, anc in ipairs(self._lang:getAncestors()) do
table.insert(ret, {name = "Kategorî:Zimanên kreyolî yan pîdgînî li ser bingeha " .. anc:getCanonicalName(), sort = canonicalName})
end
elseif famCode == "art" then
table.insert(ret, {name = "Kategorî:Zimanên çêkirî", sort = canonicalName})
-- if self._lang:hasType("appendix-constructed") then
-- table.insert(ret, {name = "Kategorî:Zimanên çêkirî di pêvekan de", sort = canonicalName})
-- else
-- table.insert(ret, {name = "Kategorî:Zimanên çêkirî", sort = canonicalName})
-- end
for _, anc in ipairs(self._lang:getAncestors()) do
table.insert(ret, {name = "Kategorî:Li ser bingeha " .. anc:getCanonicalName() .. " zimanên çêkirî", sort = canonicalName})
end
else
table.insert(ret, {name = "Kategorî:" .. mw.getContentLanguage():ucfirst(fam:getCategoryName()), sort = canonicalName})
if self._lang:hasType("reconstructed") then
table.insert(ret, {name = "Kategorî:Zimanên jinûvesazkirî", sort = (mw.ustring.gsub(canonicalName, "^Proto%-", ""))})
end
end
if self._lang:hasTranslit() then
table.insert(ret, {name = "Kategorî:Zimanên bi transliterasyona xweser", sort = canonicalName})
end
return ret
end
function Category:getUmbrella()
return nil
end
return export
jv30ooj0ih8by4wsqqgma5ch20s09et
6291618
6291617
2026-07-26T23:06:49Z
Wikihez
7344
6291618
Scribunto
text/plain
local export = {}
local new_title = mw.title.new
local ucfirst = require("Module:string utilities").ucfirst
local split = require("Module:string utilities").split
local m_languages = require("Module:languages")
local m_sc_getByCode = require("Module:scripts").getByCode
local m_table = require("Module:table")
local parse_utilities_module = "Module:parse utilities"
local concat = table.concat
local insert = table.insert
local reverse_ipairs = m_table.reverseIpairs
local serial_comma_join = m_table.serialCommaJoin
local size = m_table.size
local sorted_pairs = m_table.sortedPairs
local to_json = require("Module:JSON").toJSON
local Hang = m_sc_getByCode("Hang")
local Hani = m_sc_getByCode("Hani")
local Hira = m_sc_getByCode("Hira")
local Hrkt = m_sc_getByCode("Hrkt")
local Kana = m_sc_getByCode("Kana")
local function makeCategoryLink(object)
return "[[:Kategorî:" .. object:getCategoryName() .. "|" .. object:getCanonicalName() .. "]]"
end
local function linkbox(lang, setwiki, setwikt, setsister, entryname)
local wiktionarylinks = "''Tine.''"
local canonicalName = lang:getCanonicalName()
local wikimediaLanguages = lang:getWikimediaLanguages()
local categoryName = lang:getCategoryName()
local categoryName = mw.ustring.lower( categoryName )
local wikipediaArticle = setwiki or lang:getWikipediaArticle()
if setwikt then
require "Modul:debug".track "langcatboiler/setwikt"
if setwikt == "-" then
require "Modul:debug".track "langcatboiler/setwikt/hyphen"
end
end
if setwikt ~= "-" and wikimediaLanguages and wikimediaLanguages[1] then
wiktionarylinks = {}
for _, wikimedialang in ipairs(wikimediaLanguages) do
table.insert(wiktionarylinks,
(wikimedialang:getCanonicalName() ~= canonicalName and "(''" .. wikimedialang:getCanonicalName() .. "'') " or "") ..
"'''[[:" .. wikimedialang:getCode() .. ":|" .. wikimedialang:getCode() .. ".wiktionary.org]]'''")
end
wiktionarylinks = table.concat(wiktionarylinks, "<br/>")
end
local plural = wikimediaLanguages[2] and "s" or ""
return table.concat{
[=[<div class="tablo-flip" style="border: solid var(--wikt-palette-grey, #aaa) 1px; margin: 1 1 1 1; background: var(--wikt-palette-lightergrey, #f9f9f9); width: 270px; padding: 5px; margin: 5px;">
<div style="text-align: center; margin-bottom: 10px; margin-top: 5px">'''Zimanê ]=], categoryName, [=['''</div>
{| style="font-size: 90%; background: var(--wikt-palette-lightergrey, #f9f9f9);"
|-
| style="vertical-align: middle; height: 35px; width: 35px;" | [[File:Wiktionary-logo-v2.svg|35px|none|Wiktionary]]
|| ''Çapa ]=], categoryName, [=[ ya Wîkîferhengê''
|-
| colspan="2" style="padding-left: 10px; border-bottom: 1px solid var(--wikt-palette-lightgrey, lightgray);" | ]=], wiktionarylinks, [=[
|-
| style="vertical-align: middle; height: 35px" | [[File:Wikipedia-logo.png|35px|none|Wikipedia]]
|| ''Meqaleya Wîkîpediyê ya li ser ]=], categoryName, [=[''
|-
| colspan="2" style="padding-left: 10px; border-bottom: 1px solid var(--wikt-palette-lightgrey, lightgray);" | ]=], (setwiki == "-" and "''Tine.''" or "'''[[w:" .. wikipediaArticle .. "|" .. wikipediaArticle .. "]]'''"), [=[
|-
| style="vertical-align: middle; height: 35px" | [[File:Open book nae 02.svg|35px|none|Made]]
|| Madeya Wîkîferhengê ya li ser ]=], categoryName,[=[
|-
| colspan="2" style="padding-left: 10px;" | ''']=], require("Modul:links").full_link({lang = require("Modul:languages").getByCode("ku"), term = categoryName}), [=['''
|}
</div>]=]
}
end
local function edit_link(title, text)
return '<span class="plainlinks">['
.. tostring(mw.uri.fullUrl(title, { action = "edit" }))
.. ' ' .. text .. ']</span>'
end
-- Should perhaps use wiki syntax.
local function infobox(lang)
local ret = {}
insert(ret, '<table class="wikitable language-category-info"')
local raw_data = lang:getData("extra")
if raw_data then
local replacements = {
[1] = "canonical-name",
[2] = "wikidata-item",
[3] = "family",
[4] = "scripts",
}
local function replacer(letter1, letter2)
return letter1:lower() .. "-" .. letter2:lower()
end
-- For each key in the language data modules, returns a descriptive
-- kebab-case version (containing ASCII lowercase words separated
-- by hyphens).
local function kebab_case(key)
key = replacements[key] or key
key = key:gsub("(%l)(%u)", replacer):gsub("(%l)_(%l)", replacer)
return key
end
local compress = {compress = true}
local function html_attribute_encode(str)
str = to_json(str, compress)
:gsub('"', """)
-- & in attributes is automatically escaped.
-- :gsub("&", "&")
:gsub("<", "<")
:gsub(">", ">")
return str
end
insert(ret, ' data-code="' .. lang:getCode() .. '"')
for k, v in sorted_pairs(raw_data) do
insert(ret, " data-" .. kebab_case(k)
.. '="'
.. html_attribute_encode(v)
.. '"')
end
end
insert(ret, '>\n')
insert(ret, '<tr class="language-category-data">\n<th colspan="2">'
.. edit_link(lang:getDataModuleName(), "Datayên vî zimanî biguhêre")
.. "</th>\n</tr>\n")
insert(ret, "<tr>\n<th>Navê zimanî</th><td>" .. ucfirst(lang:getCanonicalName()) .. "</td>\n</tr>\n")
local otherNames = lang:getOtherNames()
if otherNames then
local names = {}
for _, name in ipairs(otherNames) do
insert(names, "<li>" .. ucfirst(name) .. "</li>")
end
if #names > 0 then
insert(ret, "<tr>\n<th>Navên din</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
local aliases = lang:getAliases()
if aliases then
local names = {}
for _, name in ipairs(aliases) do
insert(names, "<li>" ..ucfirst(name) .. "</li>")
end
if #names > 0 then
insert(ret, "<tr>\n<th>Bernav</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
local varieties = lang:getVarieties()
if varieties then
local names = {}
for _, name in ipairs(varieties) do
if type(name) == "string" then
insert(names, "<li>" .. ucfirst(name) .. "</li>")
else
assert(type(name) == "table")
local first_var
local subvars = {}
for i, var in ipairs(name) do
if i == 1 then
first_var = var
else
insert(subvars, "<li>" .. ucfirst(var) .. "</li>")
end
end
if #subvars > 0 then
insert(names, "<li><dl><dt>" .. first_var .. "</dt>\n<dd><ul>" .. concat(subvars, "\n") .. "</ul></dd></dl></li>")
elseif first_var then
insert(names, "<li>" .. first_var .. "</li>")
end
end
end
if #names > 0 then
insert(ret, "<tr>\n<th>Varyant</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
insert(ret, "<tr>\n<th>[[WF:LZ|Koda zimanî]]</th><td><code>" .. lang:getCode() .. "</code></td>\n</tr>\n")
insert(ret, "<tr>\n<th>[[WF:Lîsteya malbatan|Malbata zimanî]]</th>\n")
local fam = lang:getFamily()
local famCode = fam and fam:getCode()
if not fam then
insert(ret, "<td>nehatiye sinifandin</td>")
elseif famCode == "qfa-iso" then
insert(ret, "<td>[[:Kategorî:Zimanên îzole|Zimanekî îzole]]</td>")
elseif famCode == "qfa-mix" then
insert(ret, "<td>[[:Kategorî:Zimanên têkilkirî|Zimanekî têkilkirî]]</td>")
elseif famCode == "sgn" then
insert(ret, "<td>[[:Kategorî:Zimanên destan|Zimanê destan]]</td>")
elseif famCode == "crp" then
insert(ret, "<td>[[:Kategorî:Zimanên kreyolî yan pîdgînî|Kreyolî yan pîdgînî]]</td>")
elseif famCode == "art" then
insert(ret, "<td>[[:Kategorî:Zimanên çêkirî|Zimanekî çêkirî]]</td>")
else
insert(ret, "<td>" .. ucfirst(fam:makeCategoryLink()) .. "</td>")
end
insert(ret, "\n</tr>\n<tr>\n<th>Zimanên pêşî</th>\n<td>")
local ancestors = lang:getAncestors()
if ancestors[2] then
local ancestorList = {}
for i, anc in ipairs(ancestors) do
ancestorList[i] = "<li>" .. anc:makeCategoryLink() .. "</li>"
end
insert(ret, "<ul>\n" .. concat(ancestorList, "\n") .. "</ul>")
else
local ancestorChain = lang:getAncestorChainOld()
if ancestorChain[1] then
local chain = {}
for _, anc in reverse_ipairs(ancestorChain) do
insert(chain, "<li>" .. anc:makeCategoryLink() .. "</li>")
end
insert(ret, "<ul>\n" .. concat(chain, "\n<ul>\n") .. ("</ul>"):rep(#chain))
else
insert(ret, "unknown")
end
end
insert(ret, "</td>\n</tr>\n")
local scripts = lang:getScripts()
if scripts[1] then
local script_text = {}
local function makeScriptLine(sc)
local code = sc:getCode()
local url = tostring(mw.uri.fullUrl('Special:Search', {
search = 'contentmodel:css insource:"' .. code
.. '" insource:/\\.' .. code .. '/',
ns8 = '1'
}))
return sc:getCanonicalName()
.. ' (<span class="plainlinks" title="Search for stylesheets referencing this script">[' .. url .. ' <code>' .. code .. '</code>]</span>)'
end
local function add_Hrkt(text)
insert(text, "<li>" .. makeScriptLine(Hrkt))
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hira) .. "</li>")
insert(text, "<li>" .. makeScriptLine(Kana) .. "</li>")
insert(text, "</ul>")
insert(text, "</li>")
end
for _, sc in ipairs(scripts) do
local text = {}
local code = sc:getCode()
if code == "Hrkt" then
add_Hrkt(text)
else
insert(text, "<li>" .. makeScriptLine(sc))
if code == "Jpan" then
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hani) .. "</li>")
add_Hrkt(text)
insert(text, "</ul>")
elseif code == "Kore" then
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hang) .. "</li>")
insert(text, "<li>" .. makeScriptLine(Hani) .. "</li>")
insert(text, "</ul>")
end
insert(text, "</li>")
end
insert(script_text, concat(text, "\n"))
end
insert(ret, "<tr>\n<th>[[Modul:scripts/data|Skrîpt]]</th>\n<td><ul>\n" .. concat(script_text, "\n") .. "</ul></td>\n</tr>\n")
else
insert(ret, "<tr>\n<th>[[Modul:scripts/data|Skrîpt]]</th>\n<td>nehatiye nivîsin</td>\n</tr>\n")
end
local function add_module_info(raw_data, heading)
if raw_data then
local scripts = lang:getScriptCodes()
local module_info, add = {}, false
if type(raw_data) == "string" then
insert(module_info,
("[[Module:%s]]"):format(raw_data))
add = true
else
local raw_data_type = type(raw_data)
if raw_data_type == "table" and size(scripts) == 1 and type(raw_data[scripts[1]]) == "string" then
insert(module_info,
("[[Module:%s]]"):format(raw_data[scripts[1]]))
add = true
elseif raw_data_type == "table" then
insert(module_info, "<ul>")
for script, data in sorted_pairs(raw_data) do
if type(data) == "string" and m_sc_getByCode(script) then
insert(module_info, ("<li><code>%s</code>: [[Module:%s]]</li>"):format(script, data))
end
end
insert(module_info, "</ul>")
add = size(module_info) > 2
end
end
if add then
insert(ret, [=[
<tr>
<th>]=] .. heading .. [=[</th>
<td>]=] .. concat(module_info) .. [=[</td>
</tr>
]=])
end
end
end
add_module_info(raw_data.generate_forms, "Form-generating<br>module")
add_module_info(raw_data.translit, "Modula<br>translîterasyonê")
add_module_info(raw_data.display_text, "Display text<br>module")
add_module_info(raw_data.entry_name, "Entry name<br>module")
add_module_info(raw_data.sort_key, "Modula<br>rêzkirinê")
local wikidataItem = lang:getWikidataItem()
if lang:getWikidataItem() and mw.wikibase then
local URL = mw.wikibase.getEntityUrl(wikidataItem)
local link
if URL then
link = '[' .. URL .. ' ' .. wikidataItem .. ']'
else
link = '<span class="error">Invalid Wikidata item: <code>' .. wikidataItem .. '</code></span>'
end
insert(ret, "<tr><th>Wîkîdata</th><td>" .. link .. "</td></tr>")
end
insert(ret, "</table>")
return concat(ret)
end
local function NavFrame(content, title)
return '<div class="NavFrame"><div class="NavHead">'
.. (title or '{{{title}}}') .. '</div>'
.. '<div class="NavContent" style="text-align: left;">'
.. content
.. '</div></div>'
end
function export.country_categories(frame)
end
-- Category object
local Category = {}
Category.__index = Category
function Category.new(info)
for key, val in pairs(info) do
if not (key == "code" or key == "entryname" or key == "setsister" or key == "setwiki" or key == "setwikt") then
error("The parameter \"" .. key .. "\" was not recognized.")
end
end
local self = setmetatable({}, Category)
self._info = info
if not self._info.code then
error("Koda zimanî nehatiye nivîsîn.")
else
self._lang = require("Modul:languages").getByCode(self._info.code) or error("Koda zimanî \"" .. self._info.code .. "\" ne guncav e.")
end
return self
end
export.new = Category.new
function Category:getInfo()
return self._info
end
function Category:getBreadcrumbName()
return self._lang:getCanonicalName()
end
function Category:getDataModule()
return "Modul:dara kategoriyan/zimankatbike"
end
function Category:canBeEmpty()
return false
end
function Category:isHidden()
return false
end
function Category:getCategoryName()
return mw.getContentLanguage():ucfirst(self._lang:getCategoryName())
end
function Category:getDescription()
if self._lang:getCode() == "und" then
return
"Kategoriya sereke ya '''" .. self._lang:getCategoryName() .. "''', represented in Wiktionary by the [[Wiktionary:Languages|code]] '''" .. self._lang:getCode() .. "'''. " ..
"This language contains terms in historical writing, whose meaning has not yet been determined by scholars."
end
local canonicalName = self._lang:getCanonicalName()
local ret = linkbox(self._lang, self._info.setwiki, self._info.setwikt, self._info.setsister, self._info.entryname)
ret = ret .. "Ev ji bo zimanê '''" .. mw.ustring.lower(self._lang:getCategoryName()) .. "''' kategoriya sereke ye.\n\nDanasîna zimanî:\n\n" .. infobox(self._lang)
if self._lang:hasType("reconstructed") then
ret = ret .. "\n\n" ..
mw.getContentLanguage():ucfirst(canonicalName) .. " zimanekî ji nû ve sazkirî ye. Lewra peyvên di vê rûpelê de yekser nehatine peyitandin." ..
" Lê belê li gorî rêbaza berawirdkirinê piştraskirî ne û tên bawerkirin ku di wextekî de hebûn.\n\n" ..
"Peyvên bi zimanê " .. canonicalName ..
" nabe ku cihê xwe valahiya nav ya sereke de bigrin, van peyvan divê bi [[Wîkîferheng:Jinûvesazî|'''Jinûvesazî:''']] dest pê bikin."
-- elseif self._lang:hasType("appendix-constructed") then
-- ret = ret .. "\n\n" ..
-- ucfirst(canonicalName) .. " zimanekî çêkirî ye. Loma " ..
-- "divê madeyên bi " .. canonicalName ..
-- " di valahiya nav a Pêvek: de werin qeydkirin. " ..
-- "Ji bo hemû madeyan binêre [[Pêvek:" .. ucfirst(canonicalName) .. "]]."
end
ret = ret .. " __EXPECTUNUSEDCATEGORY__" -- it's OK if this category is empty
return ret
end
function Category:getParents()
local canonicalName = self._lang:getCanonicalName()
local ret = {{name = "Kategorî:Hemû ziman", sort = canonicalName}}
local fam = self._lang:getFamily()
local famCode = fam and fam:getCode()
if not fam then
table.insert(ret, {name = "Kategorî:Zimanên ku nehatine sinifandin", sort = canonicalName})
elseif famCode == "qfa-iso" then
table.insert(ret, {name = "Kategorî:Zimanên îzole", sort = canonicalName})
elseif famCode == "qfa-mix" then
table.insert(ret, {name = "Kategorî:Zimanên têkilkirî", sort = canonicalName})
elseif famCode == "sgn" then
table.insert(ret, {name = "Kategorî:Zimanên destan", sort = canonicalName})
elseif famCode == "crp" then
table.insert(ret, {name = "Kategorî:Zimanên kreyolî yan pîdgînî", sort = canonicalName})
for _, anc in ipairs(self._lang:getAncestors()) do
table.insert(ret, {name = "Kategorî:Zimanên kreyolî yan pîdgînî li ser bingeha " .. anc:getCanonicalName(), sort = canonicalName})
end
elseif famCode == "art" then
table.insert(ret, {name = "Kategorî:Zimanên çêkirî", sort = canonicalName})
-- if self._lang:hasType("appendix-constructed") then
-- table.insert(ret, {name = "Kategorî:Zimanên çêkirî di pêvekan de", sort = canonicalName})
-- else
-- table.insert(ret, {name = "Kategorî:Zimanên çêkirî", sort = canonicalName})
-- end
for _, anc in ipairs(self._lang:getAncestors()) do
table.insert(ret, {name = "Kategorî:Li ser bingeha " .. anc:getCanonicalName() .. " zimanên çêkirî", sort = canonicalName})
end
else
table.insert(ret, {name = "Kategorî:" .. mw.getContentLanguage():ucfirst(fam:getCategoryName()), sort = canonicalName})
if self._lang:hasType("reconstructed") then
table.insert(ret, {name = "Kategorî:Zimanên jinûvesazkirî", sort = (mw.ustring.gsub(canonicalName, "^Proto%-", ""))})
end
end
if self._lang:hasTranslit() then
table.insert(ret, {name = "Kategorî:Zimanên bi transliterasyona xweser", sort = canonicalName})
end
return ret
end
function Category:getUmbrella()
return nil
end
return export
1iahopromko3khwppr6w6wi4m3ru4ui
6291619
6291618
2026-07-26T23:17:04Z
Wikihez
7344
6291619
Scribunto
text/plain
local export = {}
local new_title = mw.title.new
local ucfirst = require("Module:string utilities").ucfirst
local split = require("Module:string utilities").split
local m_languages = require("Module:languages")
local m_sc_getByCode = require("Module:scripts").getByCode
local m_table = require("Module:table")
local parse_utilities_module = "Module:parse utilities"
local concat = table.concat
local insert = table.insert
local reverse_ipairs = m_table.reverseIpairs
local serial_comma_join = m_table.serialCommaJoin
local size = m_table.size
local sorted_pairs = m_table.sortedPairs
local to_json = require("Module:JSON").toJSON
local Hang = m_sc_getByCode("Hang")
local Hani = m_sc_getByCode("Hani")
local Hira = m_sc_getByCode("Hira")
local Hrkt = m_sc_getByCode("Hrkt")
local Kana = m_sc_getByCode("Kana")
local function makeCategoryLink(object)
return "[[:Kategorî:" .. object:getCategoryName() .. "|" .. object:getCanonicalName() .. "]]"
end
local function linkbox(lang, setwiki, setwikt, setsister, entryname)
local wiktionarylinks = "''Tine.''"
local canonicalName = lang:getCanonicalName()
local wikimediaLanguages = lang:getWikimediaLanguages()
local categoryName = lang:getCategoryName()
local categoryName = mw.ustring.lower( categoryName )
local wikipediaArticle = setwiki or lang:getWikipediaArticle()
if setwikt then
require "Modul:debug".track "langcatboiler/setwikt"
if setwikt == "-" then
require "Modul:debug".track "langcatboiler/setwikt/hyphen"
end
end
if setwikt ~= "-" and wikimediaLanguages and wikimediaLanguages[1] then
wiktionarylinks = {}
for _, wikimedialang in ipairs(wikimediaLanguages) do
table.insert(wiktionarylinks,
(wikimedialang:getCanonicalName() ~= canonicalName and "(''" .. wikimedialang:getCanonicalName() .. "'') " or "") ..
"'''[[:" .. wikimedialang:getCode() .. ":|" .. wikimedialang:getCode() .. ".wiktionary.org]]'''")
end
wiktionarylinks = table.concat(wiktionarylinks, "<br/>")
end
local plural = wikimediaLanguages[2] and "s" or ""
return table.concat{
[=[<div class="tablo-flip standard-box" style="margin: 0 0 .5em 1em; width: 270px;">
<div style="text-align: center; margin-bottom: 10px; margin-top: 5px">'''Zimanê ]=], categoryName, [=['''</div>
{| style="font-size: 90%; background: var(--wikt-palette-paleblue, #f8f9fa);"
|-
| style="vertical-align: middle; height: 35px; width: 35px;" | [[File:Wiktionary-logo-v2.svg|35px|none|Wiktionary]]
|| ''Çapa ]=], categoryName, [=[ ya Wîkîferhengê''
|-
| colspan="2" style="padding-left: 10px; border-bottom: 1px solid var(--wikt-palette-lightgrey, lightgray);" | ]=], wiktionarylinks, [=[
|-
| style="vertical-align: middle; height: 35px" | [[File:Wikipedia-logo.png|35px|none|Wikipedia]]
|| ''Meqaleya Wîkîpediyê ya li ser ]=], categoryName, [=[''
|-
| colspan="2" style="padding-left: 10px; border-bottom: 1px solid var(--wikt-palette-lightgrey, lightgray);" | ]=], (setwiki == "-" and "''Tine.''" or "'''[[w:" .. wikipediaArticle .. "|" .. wikipediaArticle .. "]]'''"), [=[
|-
| style="vertical-align: middle; height: 35px" | [[File:Open book nae 02.svg|35px|none|Made]]
|| Madeya Wîkîferhengê ya li ser ]=], categoryName,[=[
|-
| colspan="2" style="padding-left: 10px;" | ''']=], require("Modul:links").full_link({lang = require("Modul:languages").getByCode("ku"), term = categoryName}), [=['''
|}
</div>]=]
}
end
local function edit_link(title, text)
return '<span class="plainlinks">['
.. tostring(mw.uri.fullUrl(title, { action = "edit" }))
.. ' ' .. text .. ']</span>'
end
-- Should perhaps use wiki syntax.
local function infobox(lang)
local ret = {}
insert(ret, '<table class="wikitable language-category-info"')
local raw_data = lang:getData("extra")
if raw_data then
local replacements = {
[1] = "canonical-name",
[2] = "wikidata-item",
[3] = "family",
[4] = "scripts",
}
local function replacer(letter1, letter2)
return letter1:lower() .. "-" .. letter2:lower()
end
-- For each key in the language data modules, returns a descriptive
-- kebab-case version (containing ASCII lowercase words separated
-- by hyphens).
local function kebab_case(key)
key = replacements[key] or key
key = key:gsub("(%l)(%u)", replacer):gsub("(%l)_(%l)", replacer)
return key
end
local compress = {compress = true}
local function html_attribute_encode(str)
str = to_json(str, compress)
:gsub('"', """)
-- & in attributes is automatically escaped.
-- :gsub("&", "&")
:gsub("<", "<")
:gsub(">", ">")
return str
end
insert(ret, ' data-code="' .. lang:getCode() .. '"')
for k, v in sorted_pairs(raw_data) do
insert(ret, " data-" .. kebab_case(k)
.. '="'
.. html_attribute_encode(v)
.. '"')
end
end
insert(ret, '>\n')
insert(ret, '<tr class="language-category-data">\n<th colspan="2">'
.. edit_link(lang:getDataModuleName(), "Datayên vî zimanî biguhêre")
.. "</th>\n</tr>\n")
insert(ret, "<tr>\n<th>Navê zimanî</th><td>" .. ucfirst(lang:getCanonicalName()) .. "</td>\n</tr>\n")
local otherNames = lang:getOtherNames()
if otherNames then
local names = {}
for _, name in ipairs(otherNames) do
insert(names, "<li>" .. ucfirst(name) .. "</li>")
end
if #names > 0 then
insert(ret, "<tr>\n<th>Navên din</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
local aliases = lang:getAliases()
if aliases then
local names = {}
for _, name in ipairs(aliases) do
insert(names, "<li>" ..ucfirst(name) .. "</li>")
end
if #names > 0 then
insert(ret, "<tr>\n<th>Bernav</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
local varieties = lang:getVarieties()
if varieties then
local names = {}
for _, name in ipairs(varieties) do
if type(name) == "string" then
insert(names, "<li>" .. ucfirst(name) .. "</li>")
else
assert(type(name) == "table")
local first_var
local subvars = {}
for i, var in ipairs(name) do
if i == 1 then
first_var = var
else
insert(subvars, "<li>" .. ucfirst(var) .. "</li>")
end
end
if #subvars > 0 then
insert(names, "<li><dl><dt>" .. first_var .. "</dt>\n<dd><ul>" .. concat(subvars, "\n") .. "</ul></dd></dl></li>")
elseif first_var then
insert(names, "<li>" .. first_var .. "</li>")
end
end
end
if #names > 0 then
insert(ret, "<tr>\n<th>Varyant</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
insert(ret, "<tr>\n<th>[[WF:LZ|Koda zimanî]]</th><td><code>" .. lang:getCode() .. "</code></td>\n</tr>\n")
insert(ret, "<tr>\n<th>[[WF:Lîsteya malbatan|Malbata zimanî]]</th>\n")
local fam = lang:getFamily()
local famCode = fam and fam:getCode()
if not fam then
insert(ret, "<td>nehatiye sinifandin</td>")
elseif famCode == "qfa-iso" then
insert(ret, "<td>[[:Kategorî:Zimanên îzole|Zimanekî îzole]]</td>")
elseif famCode == "qfa-mix" then
insert(ret, "<td>[[:Kategorî:Zimanên têkilkirî|Zimanekî têkilkirî]]</td>")
elseif famCode == "sgn" then
insert(ret, "<td>[[:Kategorî:Zimanên destan|Zimanê destan]]</td>")
elseif famCode == "crp" then
insert(ret, "<td>[[:Kategorî:Zimanên kreyolî yan pîdgînî|Kreyolî yan pîdgînî]]</td>")
elseif famCode == "art" then
insert(ret, "<td>[[:Kategorî:Zimanên çêkirî|Zimanekî çêkirî]]</td>")
else
insert(ret, "<td>" .. ucfirst(fam:makeCategoryLink()) .. "</td>")
end
insert(ret, "\n</tr>\n<tr>\n<th>Zimanên pêşî</th>\n<td>")
local ancestors = lang:getAncestors()
if ancestors[2] then
local ancestorList = {}
for i, anc in ipairs(ancestors) do
ancestorList[i] = "<li>" .. anc:makeCategoryLink() .. "</li>"
end
insert(ret, "<ul>\n" .. concat(ancestorList, "\n") .. "</ul>")
else
local ancestorChain = lang:getAncestorChainOld()
if ancestorChain[1] then
local chain = {}
for _, anc in reverse_ipairs(ancestorChain) do
insert(chain, "<li>" .. anc:makeCategoryLink() .. "</li>")
end
insert(ret, "<ul>\n" .. concat(chain, "\n<ul>\n") .. ("</ul>"):rep(#chain))
else
insert(ret, "unknown")
end
end
insert(ret, "</td>\n</tr>\n")
local scripts = lang:getScripts()
if scripts[1] then
local script_text = {}
local function makeScriptLine(sc)
local code = sc:getCode()
local url = tostring(mw.uri.fullUrl('Special:Search', {
search = 'contentmodel:css insource:"' .. code
.. '" insource:/\\.' .. code .. '/',
ns8 = '1'
}))
return sc:getCanonicalName()
.. ' (<span class="plainlinks" title="Search for stylesheets referencing this script">[' .. url .. ' <code>' .. code .. '</code>]</span>)'
end
local function add_Hrkt(text)
insert(text, "<li>" .. makeScriptLine(Hrkt))
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hira) .. "</li>")
insert(text, "<li>" .. makeScriptLine(Kana) .. "</li>")
insert(text, "</ul>")
insert(text, "</li>")
end
for _, sc in ipairs(scripts) do
local text = {}
local code = sc:getCode()
if code == "Hrkt" then
add_Hrkt(text)
else
insert(text, "<li>" .. makeScriptLine(sc))
if code == "Jpan" then
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hani) .. "</li>")
add_Hrkt(text)
insert(text, "</ul>")
elseif code == "Kore" then
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hang) .. "</li>")
insert(text, "<li>" .. makeScriptLine(Hani) .. "</li>")
insert(text, "</ul>")
end
insert(text, "</li>")
end
insert(script_text, concat(text, "\n"))
end
insert(ret, "<tr>\n<th>[[Modul:scripts/data|Skrîpt]]</th>\n<td><ul>\n" .. concat(script_text, "\n") .. "</ul></td>\n</tr>\n")
else
insert(ret, "<tr>\n<th>[[Modul:scripts/data|Skrîpt]]</th>\n<td>nehatiye nivîsin</td>\n</tr>\n")
end
local function add_module_info(raw_data, heading)
if raw_data then
local scripts = lang:getScriptCodes()
local module_info, add = {}, false
if type(raw_data) == "string" then
insert(module_info,
("[[Module:%s]]"):format(raw_data))
add = true
else
local raw_data_type = type(raw_data)
if raw_data_type == "table" and size(scripts) == 1 and type(raw_data[scripts[1]]) == "string" then
insert(module_info,
("[[Module:%s]]"):format(raw_data[scripts[1]]))
add = true
elseif raw_data_type == "table" then
insert(module_info, "<ul>")
for script, data in sorted_pairs(raw_data) do
if type(data) == "string" and m_sc_getByCode(script) then
insert(module_info, ("<li><code>%s</code>: [[Module:%s]]</li>"):format(script, data))
end
end
insert(module_info, "</ul>")
add = size(module_info) > 2
end
end
if add then
insert(ret, [=[
<tr>
<th>]=] .. heading .. [=[</th>
<td>]=] .. concat(module_info) .. [=[</td>
</tr>
]=])
end
end
end
add_module_info(raw_data.generate_forms, "Form-generating<br>module")
add_module_info(raw_data.translit, "Modula<br>translîterasyonê")
add_module_info(raw_data.display_text, "Display text<br>module")
add_module_info(raw_data.entry_name, "Entry name<br>module")
add_module_info(raw_data.sort_key, "Modula<br>rêzkirinê")
local wikidataItem = lang:getWikidataItem()
if lang:getWikidataItem() and mw.wikibase then
local URL = mw.wikibase.getEntityUrl(wikidataItem)
local link
if URL then
link = '[' .. URL .. ' ' .. wikidataItem .. ']'
else
link = '<span class="error">Invalid Wikidata item: <code>' .. wikidataItem .. '</code></span>'
end
insert(ret, "<tr><th>Wîkîdata</th><td>" .. link .. "</td></tr>")
end
insert(ret, "</table>")
return concat(ret)
end
local function NavFrame(content, title)
return '<div class="NavFrame"><div class="NavHead">'
.. (title or '{{{title}}}') .. '</div>'
.. '<div class="NavContent" style="text-align: left;">'
.. content
.. '</div></div>'
end
function export.country_categories(frame)
end
-- Category object
local Category = {}
Category.__index = Category
function Category.new(info)
for key, val in pairs(info) do
if not (key == "code" or key == "entryname" or key == "setsister" or key == "setwiki" or key == "setwikt") then
error("The parameter \"" .. key .. "\" was not recognized.")
end
end
local self = setmetatable({}, Category)
self._info = info
if not self._info.code then
error("Koda zimanî nehatiye nivîsîn.")
else
self._lang = require("Modul:languages").getByCode(self._info.code) or error("Koda zimanî \"" .. self._info.code .. "\" ne guncav e.")
end
return self
end
export.new = Category.new
function Category:getInfo()
return self._info
end
function Category:getBreadcrumbName()
return self._lang:getCanonicalName()
end
function Category:getDataModule()
return "Modul:dara kategoriyan/zimankatbike"
end
function Category:canBeEmpty()
return false
end
function Category:isHidden()
return false
end
function Category:getCategoryName()
return mw.getContentLanguage():ucfirst(self._lang:getCategoryName())
end
function Category:getDescription()
if self._lang:getCode() == "und" then
return
"Kategoriya sereke ya '''" .. self._lang:getCategoryName() .. "''', represented in Wiktionary by the [[Wiktionary:Languages|code]] '''" .. self._lang:getCode() .. "'''. " ..
"This language contains terms in historical writing, whose meaning has not yet been determined by scholars."
end
local canonicalName = self._lang:getCanonicalName()
local ret = linkbox(self._lang, self._info.setwiki, self._info.setwikt, self._info.setsister, self._info.entryname)
ret = ret .. "Ev ji bo zimanê '''" .. mw.ustring.lower(self._lang:getCategoryName()) .. "''' kategoriya sereke ye.\n\nDanasîna zimanî:\n\n" .. infobox(self._lang)
if self._lang:hasType("reconstructed") then
ret = ret .. "\n\n" ..
mw.getContentLanguage():ucfirst(canonicalName) .. " zimanekî ji nû ve sazkirî ye. Lewra peyvên di vê rûpelê de yekser nehatine peyitandin." ..
" Lê belê li gorî rêbaza berawirdkirinê piştraskirî ne û tên bawerkirin ku di wextekî de hebûn.\n\n" ..
"Peyvên bi zimanê " .. canonicalName ..
" nabe ku cihê xwe valahiya nav ya sereke de bigrin, van peyvan divê bi [[Wîkîferheng:Jinûvesazî|'''Jinûvesazî:''']] dest pê bikin."
-- elseif self._lang:hasType("appendix-constructed") then
-- ret = ret .. "\n\n" ..
-- ucfirst(canonicalName) .. " zimanekî çêkirî ye. Loma " ..
-- "divê madeyên bi " .. canonicalName ..
-- " di valahiya nav a Pêvek: de werin qeydkirin. " ..
-- "Ji bo hemû madeyan binêre [[Pêvek:" .. ucfirst(canonicalName) .. "]]."
end
ret = ret .. " __EXPECTUNUSEDCATEGORY__" -- it's OK if this category is empty
return ret
end
function Category:getParents()
local canonicalName = self._lang:getCanonicalName()
local ret = {{name = "Kategorî:Hemû ziman", sort = canonicalName}}
local fam = self._lang:getFamily()
local famCode = fam and fam:getCode()
if not fam then
table.insert(ret, {name = "Kategorî:Zimanên ku nehatine sinifandin", sort = canonicalName})
elseif famCode == "qfa-iso" then
table.insert(ret, {name = "Kategorî:Zimanên îzole", sort = canonicalName})
elseif famCode == "qfa-mix" then
table.insert(ret, {name = "Kategorî:Zimanên têkilkirî", sort = canonicalName})
elseif famCode == "sgn" then
table.insert(ret, {name = "Kategorî:Zimanên destan", sort = canonicalName})
elseif famCode == "crp" then
table.insert(ret, {name = "Kategorî:Zimanên kreyolî yan pîdgînî", sort = canonicalName})
for _, anc in ipairs(self._lang:getAncestors()) do
table.insert(ret, {name = "Kategorî:Zimanên kreyolî yan pîdgînî li ser bingeha " .. anc:getCanonicalName(), sort = canonicalName})
end
elseif famCode == "art" then
table.insert(ret, {name = "Kategorî:Zimanên çêkirî", sort = canonicalName})
-- if self._lang:hasType("appendix-constructed") then
-- table.insert(ret, {name = "Kategorî:Zimanên çêkirî di pêvekan de", sort = canonicalName})
-- else
-- table.insert(ret, {name = "Kategorî:Zimanên çêkirî", sort = canonicalName})
-- end
for _, anc in ipairs(self._lang:getAncestors()) do
table.insert(ret, {name = "Kategorî:Li ser bingeha " .. anc:getCanonicalName() .. " zimanên çêkirî", sort = canonicalName})
end
else
table.insert(ret, {name = "Kategorî:" .. mw.getContentLanguage():ucfirst(fam:getCategoryName()), sort = canonicalName})
if self._lang:hasType("reconstructed") then
table.insert(ret, {name = "Kategorî:Zimanên jinûvesazkirî", sort = (mw.ustring.gsub(canonicalName, "^Proto%-", ""))})
end
end
if self._lang:hasTranslit() then
table.insert(ret, {name = "Kategorî:Zimanên bi transliterasyona xweser", sort = canonicalName})
end
return ret
end
function Category:getUmbrella()
return nil
end
return export
g4y0jrfqlopkoww2vxhbjzh6geo06x6
6291621
6291619
2026-07-26T23:34:49Z
Wikihez
7344
6291621
Scribunto
text/plain
local export = {}
local new_title = mw.title.new
local ucfirst = require("Module:string utilities").ucfirst
local split = require("Module:string utilities").split
local m_languages = require("Module:languages")
local m_sc_getByCode = require("Module:scripts").getByCode
local m_table = require("Module:table")
local parse_utilities_module = "Module:parse utilities"
local concat = table.concat
local insert = table.insert
local reverse_ipairs = m_table.reverseIpairs
local serial_comma_join = m_table.serialCommaJoin
local size = m_table.size
local sorted_pairs = m_table.sortedPairs
local to_json = require("Module:JSON").toJSON
local Hang = m_sc_getByCode("Hang")
local Hani = m_sc_getByCode("Hani")
local Hira = m_sc_getByCode("Hira")
local Hrkt = m_sc_getByCode("Hrkt")
local Kana = m_sc_getByCode("Kana")
local function makeCategoryLink(object)
return "[[:Kategorî:" .. object:getCategoryName() .. "|" .. object:getCanonicalName() .. "]]"
end
local function linkbox(lang, setwiki, setwikt, setsister, entryname)
local wiktionarylinks = "''Tine.''"
local canonicalName = lang:getCanonicalName()
local wikimediaLanguages = lang:getWikimediaLanguages()
local categoryName = lang:getCategoryName()
local categoryName = mw.ustring.lower( categoryName )
local wikipediaArticle = setwiki or lang:getWikipediaArticle()
if setwikt then
require "Modul:debug".track "langcatboiler/setwikt"
if setwikt == "-" then
require "Modul:debug".track "langcatboiler/setwikt/hyphen"
end
end
if setwikt ~= "-" and wikimediaLanguages and wikimediaLanguages[1] then
wiktionarylinks = {}
for _, wikimedialang in ipairs(wikimediaLanguages) do
table.insert(wiktionarylinks,
(wikimedialang:getCanonicalName() ~= canonicalName and "(''" .. wikimedialang:getCanonicalName() .. "'') " or "") ..
"'''[[:" .. wikimedialang:getCode() .. ":|" .. wikimedialang:getCode() .. ".wiktionary.org]]'''")
end
wiktionarylinks = table.concat(wiktionarylinks, "<br/>")
end
local plural = wikimediaLanguages[2] and "s" or ""
return table.concat{
[=[<div class="tablo-flip standard-box" style="margin: 0 0 .5em 1em; width: 270px;">
<div style="text-align: center; margin-bottom: 10px; margin-top: 5px">'''Zimanê ]=], categoryName, [=['''</div>
{| style="font-size: 90%; var(--wikt-palette-grey-4, lightgray);;"
|-
| style="vertical-align: middle; height: 35px; width: 35px;" | [[File:Wiktionary-logo-v2.svg|35px|none|Wiktionary]]
|| ''Çapa ]=], categoryName, [=[ ya Wîkîferhengê''
|-
| colspan="2" style="padding-left: 10px; border-bottom: 1px solid var(--wikt-palette-grey-4, lightgray);" | ]=], wiktionarylinks, [=[
|-
| style="vertical-align: middle; height: 35px" | [[File:Wikipedia-logo.png|35px|none|Wikipedia]]
|| ''Meqaleya Wîkîpediyê ya li ser ]=], categoryName, [=[''
|-
| colspan="2" style="padding-left: 10px; border-bottom: 1px solid var(--wikt-palette-grey-4, lightgray);;" | ]=], (setwiki == "-" and "''Tine.''" or "'''[[w:" .. wikipediaArticle .. "|" .. wikipediaArticle .. "]]'''"), [=[
|-
| style="vertical-align: middle; height: 35px" | [[File:Open book nae 02.svg|35px|none|Made]]
|| Madeya Wîkîferhengê ya li ser ]=], categoryName,[=[
|-
| colspan="2" style="padding-left: 10px;" | ''']=], require("Modul:links").full_link({lang = require("Modul:languages").getByCode("ku"), term = categoryName}), [=['''
|}
</div>]=]
}
end
local function edit_link(title, text)
return '<span class="plainlinks">['
.. tostring(mw.uri.fullUrl(title, { action = "edit" }))
.. ' ' .. text .. ']</span>'
end
-- Should perhaps use wiki syntax.
local function infobox(lang)
local ret = {}
insert(ret, '<table class="wikitable language-category-info"')
local raw_data = lang:getData("extra")
if raw_data then
local replacements = {
[1] = "canonical-name",
[2] = "wikidata-item",
[3] = "family",
[4] = "scripts",
}
local function replacer(letter1, letter2)
return letter1:lower() .. "-" .. letter2:lower()
end
-- For each key in the language data modules, returns a descriptive
-- kebab-case version (containing ASCII lowercase words separated
-- by hyphens).
local function kebab_case(key)
key = replacements[key] or key
key = key:gsub("(%l)(%u)", replacer):gsub("(%l)_(%l)", replacer)
return key
end
local compress = {compress = true}
local function html_attribute_encode(str)
str = to_json(str, compress)
:gsub('"', """)
-- & in attributes is automatically escaped.
-- :gsub("&", "&")
:gsub("<", "<")
:gsub(">", ">")
return str
end
insert(ret, ' data-code="' .. lang:getCode() .. '"')
for k, v in sorted_pairs(raw_data) do
insert(ret, " data-" .. kebab_case(k)
.. '="'
.. html_attribute_encode(v)
.. '"')
end
end
insert(ret, '>\n')
insert(ret, '<tr class="language-category-data">\n<th colspan="2">'
.. edit_link(lang:getDataModuleName(), "Datayên vî zimanî biguhêre")
.. "</th>\n</tr>\n")
insert(ret, "<tr>\n<th>Navê zimanî</th><td>" .. ucfirst(lang:getCanonicalName()) .. "</td>\n</tr>\n")
local otherNames = lang:getOtherNames()
if otherNames then
local names = {}
for _, name in ipairs(otherNames) do
insert(names, "<li>" .. ucfirst(name) .. "</li>")
end
if #names > 0 then
insert(ret, "<tr>\n<th>Navên din</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
local aliases = lang:getAliases()
if aliases then
local names = {}
for _, name in ipairs(aliases) do
insert(names, "<li>" ..ucfirst(name) .. "</li>")
end
if #names > 0 then
insert(ret, "<tr>\n<th>Bernav</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
local varieties = lang:getVarieties()
if varieties then
local names = {}
for _, name in ipairs(varieties) do
if type(name) == "string" then
insert(names, "<li>" .. ucfirst(name) .. "</li>")
else
assert(type(name) == "table")
local first_var
local subvars = {}
for i, var in ipairs(name) do
if i == 1 then
first_var = var
else
insert(subvars, "<li>" .. ucfirst(var) .. "</li>")
end
end
if #subvars > 0 then
insert(names, "<li><dl><dt>" .. first_var .. "</dt>\n<dd><ul>" .. concat(subvars, "\n") .. "</ul></dd></dl></li>")
elseif first_var then
insert(names, "<li>" .. first_var .. "</li>")
end
end
end
if #names > 0 then
insert(ret, "<tr>\n<th>Varyant</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
insert(ret, "<tr>\n<th>[[WF:LZ|Koda zimanî]]</th><td><code>" .. lang:getCode() .. "</code></td>\n</tr>\n")
insert(ret, "<tr>\n<th>[[WF:Lîsteya malbatan|Malbata zimanî]]</th>\n")
local fam = lang:getFamily()
local famCode = fam and fam:getCode()
if not fam then
insert(ret, "<td>nehatiye sinifandin</td>")
elseif famCode == "qfa-iso" then
insert(ret, "<td>[[:Kategorî:Zimanên îzole|Zimanekî îzole]]</td>")
elseif famCode == "qfa-mix" then
insert(ret, "<td>[[:Kategorî:Zimanên têkilkirî|Zimanekî têkilkirî]]</td>")
elseif famCode == "sgn" then
insert(ret, "<td>[[:Kategorî:Zimanên destan|Zimanê destan]]</td>")
elseif famCode == "crp" then
insert(ret, "<td>[[:Kategorî:Zimanên kreyolî yan pîdgînî|Kreyolî yan pîdgînî]]</td>")
elseif famCode == "art" then
insert(ret, "<td>[[:Kategorî:Zimanên çêkirî|Zimanekî çêkirî]]</td>")
else
insert(ret, "<td>" .. ucfirst(fam:makeCategoryLink()) .. "</td>")
end
insert(ret, "\n</tr>\n<tr>\n<th>Zimanên pêşî</th>\n<td>")
local ancestors = lang:getAncestors()
if ancestors[2] then
local ancestorList = {}
for i, anc in ipairs(ancestors) do
ancestorList[i] = "<li>" .. anc:makeCategoryLink() .. "</li>"
end
insert(ret, "<ul>\n" .. concat(ancestorList, "\n") .. "</ul>")
else
local ancestorChain = lang:getAncestorChainOld()
if ancestorChain[1] then
local chain = {}
for _, anc in reverse_ipairs(ancestorChain) do
insert(chain, "<li>" .. anc:makeCategoryLink() .. "</li>")
end
insert(ret, "<ul>\n" .. concat(chain, "\n<ul>\n") .. ("</ul>"):rep(#chain))
else
insert(ret, "unknown")
end
end
insert(ret, "</td>\n</tr>\n")
local scripts = lang:getScripts()
if scripts[1] then
local script_text = {}
local function makeScriptLine(sc)
local code = sc:getCode()
local url = tostring(mw.uri.fullUrl('Special:Search', {
search = 'contentmodel:css insource:"' .. code
.. '" insource:/\\.' .. code .. '/',
ns8 = '1'
}))
return sc:getCanonicalName()
.. ' (<span class="plainlinks" title="Search for stylesheets referencing this script">[' .. url .. ' <code>' .. code .. '</code>]</span>)'
end
local function add_Hrkt(text)
insert(text, "<li>" .. makeScriptLine(Hrkt))
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hira) .. "</li>")
insert(text, "<li>" .. makeScriptLine(Kana) .. "</li>")
insert(text, "</ul>")
insert(text, "</li>")
end
for _, sc in ipairs(scripts) do
local text = {}
local code = sc:getCode()
if code == "Hrkt" then
add_Hrkt(text)
else
insert(text, "<li>" .. makeScriptLine(sc))
if code == "Jpan" then
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hani) .. "</li>")
add_Hrkt(text)
insert(text, "</ul>")
elseif code == "Kore" then
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hang) .. "</li>")
insert(text, "<li>" .. makeScriptLine(Hani) .. "</li>")
insert(text, "</ul>")
end
insert(text, "</li>")
end
insert(script_text, concat(text, "\n"))
end
insert(ret, "<tr>\n<th>[[Modul:scripts/data|Skrîpt]]</th>\n<td><ul>\n" .. concat(script_text, "\n") .. "</ul></td>\n</tr>\n")
else
insert(ret, "<tr>\n<th>[[Modul:scripts/data|Skrîpt]]</th>\n<td>nehatiye nivîsin</td>\n</tr>\n")
end
local function add_module_info(raw_data, heading)
if raw_data then
local scripts = lang:getScriptCodes()
local module_info, add = {}, false
if type(raw_data) == "string" then
insert(module_info,
("[[Module:%s]]"):format(raw_data))
add = true
else
local raw_data_type = type(raw_data)
if raw_data_type == "table" and size(scripts) == 1 and type(raw_data[scripts[1]]) == "string" then
insert(module_info,
("[[Module:%s]]"):format(raw_data[scripts[1]]))
add = true
elseif raw_data_type == "table" then
insert(module_info, "<ul>")
for script, data in sorted_pairs(raw_data) do
if type(data) == "string" and m_sc_getByCode(script) then
insert(module_info, ("<li><code>%s</code>: [[Module:%s]]</li>"):format(script, data))
end
end
insert(module_info, "</ul>")
add = size(module_info) > 2
end
end
if add then
insert(ret, [=[
<tr>
<th>]=] .. heading .. [=[</th>
<td>]=] .. concat(module_info) .. [=[</td>
</tr>
]=])
end
end
end
add_module_info(raw_data.generate_forms, "Form-generating<br>module")
add_module_info(raw_data.translit, "Modula<br>translîterasyonê")
add_module_info(raw_data.display_text, "Display text<br>module")
add_module_info(raw_data.entry_name, "Entry name<br>module")
add_module_info(raw_data.sort_key, "Modula<br>rêzkirinê")
local wikidataItem = lang:getWikidataItem()
if lang:getWikidataItem() and mw.wikibase then
local URL = mw.wikibase.getEntityUrl(wikidataItem)
local link
if URL then
link = '[' .. URL .. ' ' .. wikidataItem .. ']'
else
link = '<span class="error">Invalid Wikidata item: <code>' .. wikidataItem .. '</code></span>'
end
insert(ret, "<tr><th>Wîkîdata</th><td>" .. link .. "</td></tr>")
end
insert(ret, "</table>")
return concat(ret)
end
local function NavFrame(content, title)
return '<div class="NavFrame"><div class="NavHead">'
.. (title or '{{{title}}}') .. '</div>'
.. '<div class="NavContent" style="text-align: left;">'
.. content
.. '</div></div>'
end
function export.country_categories(frame)
end
-- Category object
local Category = {}
Category.__index = Category
function Category.new(info)
for key, val in pairs(info) do
if not (key == "code" or key == "entryname" or key == "setsister" or key == "setwiki" or key == "setwikt") then
error("The parameter \"" .. key .. "\" was not recognized.")
end
end
local self = setmetatable({}, Category)
self._info = info
if not self._info.code then
error("Koda zimanî nehatiye nivîsîn.")
else
self._lang = require("Modul:languages").getByCode(self._info.code) or error("Koda zimanî \"" .. self._info.code .. "\" ne guncav e.")
end
return self
end
export.new = Category.new
function Category:getInfo()
return self._info
end
function Category:getBreadcrumbName()
return self._lang:getCanonicalName()
end
function Category:getDataModule()
return "Modul:dara kategoriyan/zimankatbike"
end
function Category:canBeEmpty()
return false
end
function Category:isHidden()
return false
end
function Category:getCategoryName()
return mw.getContentLanguage():ucfirst(self._lang:getCategoryName())
end
function Category:getDescription()
if self._lang:getCode() == "und" then
return
"Kategoriya sereke ya '''" .. self._lang:getCategoryName() .. "''', represented in Wiktionary by the [[Wiktionary:Languages|code]] '''" .. self._lang:getCode() .. "'''. " ..
"This language contains terms in historical writing, whose meaning has not yet been determined by scholars."
end
local canonicalName = self._lang:getCanonicalName()
local ret = linkbox(self._lang, self._info.setwiki, self._info.setwikt, self._info.setsister, self._info.entryname)
ret = ret .. "Ev ji bo zimanê '''" .. mw.ustring.lower(self._lang:getCategoryName()) .. "''' kategoriya sereke ye.\n\nDanasîna zimanî:\n\n" .. infobox(self._lang)
if self._lang:hasType("reconstructed") then
ret = ret .. "\n\n" ..
mw.getContentLanguage():ucfirst(canonicalName) .. " zimanekî ji nû ve sazkirî ye. Lewra peyvên di vê rûpelê de yekser nehatine peyitandin." ..
" Lê belê li gorî rêbaza berawirdkirinê piştraskirî ne û tên bawerkirin ku di wextekî de hebûn.\n\n" ..
"Peyvên bi zimanê " .. canonicalName ..
" nabe ku cihê xwe valahiya nav ya sereke de bigrin, van peyvan divê bi [[Wîkîferheng:Jinûvesazî|'''Jinûvesazî:''']] dest pê bikin."
-- elseif self._lang:hasType("appendix-constructed") then
-- ret = ret .. "\n\n" ..
-- ucfirst(canonicalName) .. " zimanekî çêkirî ye. Loma " ..
-- "divê madeyên bi " .. canonicalName ..
-- " di valahiya nav a Pêvek: de werin qeydkirin. " ..
-- "Ji bo hemû madeyan binêre [[Pêvek:" .. ucfirst(canonicalName) .. "]]."
end
ret = ret .. " __EXPECTUNUSEDCATEGORY__" -- it's OK if this category is empty
return ret
end
function Category:getParents()
local canonicalName = self._lang:getCanonicalName()
local ret = {{name = "Kategorî:Hemû ziman", sort = canonicalName}}
local fam = self._lang:getFamily()
local famCode = fam and fam:getCode()
if not fam then
table.insert(ret, {name = "Kategorî:Zimanên ku nehatine sinifandin", sort = canonicalName})
elseif famCode == "qfa-iso" then
table.insert(ret, {name = "Kategorî:Zimanên îzole", sort = canonicalName})
elseif famCode == "qfa-mix" then
table.insert(ret, {name = "Kategorî:Zimanên têkilkirî", sort = canonicalName})
elseif famCode == "sgn" then
table.insert(ret, {name = "Kategorî:Zimanên destan", sort = canonicalName})
elseif famCode == "crp" then
table.insert(ret, {name = "Kategorî:Zimanên kreyolî yan pîdgînî", sort = canonicalName})
for _, anc in ipairs(self._lang:getAncestors()) do
table.insert(ret, {name = "Kategorî:Zimanên kreyolî yan pîdgînî li ser bingeha " .. anc:getCanonicalName(), sort = canonicalName})
end
elseif famCode == "art" then
table.insert(ret, {name = "Kategorî:Zimanên çêkirî", sort = canonicalName})
-- if self._lang:hasType("appendix-constructed") then
-- table.insert(ret, {name = "Kategorî:Zimanên çêkirî di pêvekan de", sort = canonicalName})
-- else
-- table.insert(ret, {name = "Kategorî:Zimanên çêkirî", sort = canonicalName})
-- end
for _, anc in ipairs(self._lang:getAncestors()) do
table.insert(ret, {name = "Kategorî:Li ser bingeha " .. anc:getCanonicalName() .. " zimanên çêkirî", sort = canonicalName})
end
else
table.insert(ret, {name = "Kategorî:" .. mw.getContentLanguage():ucfirst(fam:getCategoryName()), sort = canonicalName})
if self._lang:hasType("reconstructed") then
table.insert(ret, {name = "Kategorî:Zimanên jinûvesazkirî", sort = (mw.ustring.gsub(canonicalName, "^Proto%-", ""))})
end
end
if self._lang:hasTranslit() then
table.insert(ret, {name = "Kategorî:Zimanên bi transliterasyona xweser", sort = canonicalName})
end
return ret
end
function Category:getUmbrella()
return nil
end
return export
8ys9rh5ahih86x3hgwdhi95ubijl7e4
Modul:kurterê
828
907825
6291624
6072019
2026-07-26T23:48:07Z
Wikihez
7344
6291624
Scribunto
text/plain
local export = {}
function export.raber(frame)--show
local output = {}
-- Hacky way to allow function to be called from other modules.
local args = (frame.getParent and frame:getParent().args) or frame
local demo = false
local FULLPAGENAME = mw.title.getCurrentTitle().fullText
local BASEPAGENAME, SUBPAGENAME = FULLPAGENAME:match("^(.-)/([^/]-)$")
-- BASEPAGENAME = BASEPAGENAME or FULLPAGENAME
if frame.getParent and FULLPAGENAME == frame:getParent():getTitle() then
demo = true
end
output[#output + 1] = '<table class="noprint plainlinks shortcutbox tablo-flip" style="border: 1px solid var(--wikt-palette-grey-4, #aaa); text-align: center; background: var(--wikt-palette-grey-4, lightgray);"><tr><th style="border: none; var(--wikt-palette-grey-4, lightgray);"><small>'
if args.temp then
output[#output + 1] = "[[kurterê|Kurterêya Demkî" .. (args[2] and 's' or '') .. "]]:<br/>"
else
output[#output + 1] = "[[kurterê|Kurterê" .. (args[2] and '' or '') .. "]]:<br/>"
end
local attn = false
for i, shortcut in ipairs(args) do
local title = mw.title.new(shortcut)
if not title then
output[#output + 1] = '<span class="attentionseeking">(<code>' .. mw.text.nowiki(shortcut) .. '</code>)</span><br/>'
attn = true
else
local iattn = false
local item
if not title.exists then
attn = true --preload naxebite nizanim çima. Kes dizane?
local query = 'action=edit&redlink=1&preloadtext=' .. mw.uri.encode('#BERALÎKIRIN [[' .. FULLPAGENAME .. ']]')
if title.nsText == "Şablon" then
item = '<code>{{[' .. tostring(mw.uri.fullUrl(shortcut, query)) .. ' <span style="color:#c20;">' .. title.text .. '</span>]}}</code>'
else
item = '[' .. tostring(mw.uri.fullUrl(shortcut, query)) .. ' <span style="color:#c20;">' .. shortcut .. '</span>]'
end
else
if title.nsText == "Şablon" then
item = '<code>{{[' .. tostring(mw.uri.fullUrl(shortcut, 'redirect=no')) .. ' ' .. title.text .. ']}}</code>'
else
item = '[' .. tostring(mw.uri.fullUrl(shortcut, 'redirect=no')) .. ' ' .. shortcut .. ']'
end
if not demo then
local redirectTarget = title.redirectTarget
if redirectTarget then
redirectTarget.fragment = '' -- remove fragment, if present
local targetText = redirectTarget.fullText
if title.nsText == "Şablon" then
if SUBPAGENAME == 'belge' then
iattn = (targetText ~= BASEPAGENAME)
else
iattn = (targetText ~= FULLPAGENAME)
end
else
if SUBPAGENAME == 'belge' then
iattn = not (targetText == BASEPAGENAME
or targetText == FULLPAGENAME)
else
iattn = not (targetText == FULLPAGENAME
or targetText == FULLPAGENAME .. '/belge')
end
end
end
if not (redirectTarget and redirectTarget.exists) then
iattn = true
end
end
end
if iattn then
item = '<span class="attentionseeking">' .. item .. '</span>'
end
output[#output + 1] = item .. '<br/>'
attn = attn or iattn
end
end
if not args.nocat then
if attn then
output[#output + 1] = '[[Kategorî:Daxwazên lênêrîna kurterêyan]]'
end
if SUBPAGENAME ~= 'belge' then
output[#output + 1] = '[[Kategorî:Rûpelên Wîkîferhengê bi kurterêyan]]'
end
end
output[#output + 1] = '</small></th></tr></table>'
return table.concat(output)
end
return export
grs7kijg557uicds5n98zk342amogk8
6291625
6291624
2026-07-26T23:51:51Z
Wikihez
7344
6291625
Scribunto
text/plain
local export = {}
function export.raber(frame)--show
local output = {}
-- Hacky way to allow function to be called from other modules.
local args = (frame.getParent and frame:getParent().args) or frame
local demo = false
local FULLPAGENAME = mw.title.getCurrentTitle().fullText
local BASEPAGENAME, SUBPAGENAME = FULLPAGENAME:match("^(.-)/([^/]-)$")
-- BASEPAGENAME = BASEPAGENAME or FULLPAGENAME
if frame.getParent and FULLPAGENAME == frame:getParent():getTitle() then
demo = true
end
output[#output + 1] = '<table class="noprint plainlinks shortcutbox tablo-flip" style="border: 1px solid var(--wikt-palette-grey-4, #aaa); text-align: center; background: var(--wikt-palette-grey-4, #fff) background:transparent;;"><tr><th style="border: none; "><small>'
if args.temp then
output[#output + 1] = "[[kurterê|Kurterêya Demkî" .. (args[2] and 's' or '') .. "]]:<br/>"
else
output[#output + 1] = "[[kurterê|Kurterê" .. (args[2] and '' or '') .. "]]:<br/>"
end
local attn = false
for i, shortcut in ipairs(args) do
local title = mw.title.new(shortcut)
if not title then
output[#output + 1] = '<span class="attentionseeking">(<code>' .. mw.text.nowiki(shortcut) .. '</code>)</span><br/>'
attn = true
else
local iattn = false
local item
if not title.exists then
attn = true --preload naxebite nizanim çima. Kes dizane?
local query = 'action=edit&redlink=1&preloadtext=' .. mw.uri.encode('#BERALÎKIRIN [[' .. FULLPAGENAME .. ']]')
if title.nsText == "Şablon" then
item = '<code>{{[' .. tostring(mw.uri.fullUrl(shortcut, query)) .. ' <span style="color:#c20;">' .. title.text .. '</span>]}}</code>'
else
item = '[' .. tostring(mw.uri.fullUrl(shortcut, query)) .. ' <span style="color:#c20;">' .. shortcut .. '</span>]'
end
else
if title.nsText == "Şablon" then
item = '<code>{{[' .. tostring(mw.uri.fullUrl(shortcut, 'redirect=no')) .. ' ' .. title.text .. ']}}</code>'
else
item = '[' .. tostring(mw.uri.fullUrl(shortcut, 'redirect=no')) .. ' ' .. shortcut .. ']'
end
if not demo then
local redirectTarget = title.redirectTarget
if redirectTarget then
redirectTarget.fragment = '' -- remove fragment, if present
local targetText = redirectTarget.fullText
if title.nsText == "Şablon" then
if SUBPAGENAME == 'belge' then
iattn = (targetText ~= BASEPAGENAME)
else
iattn = (targetText ~= FULLPAGENAME)
end
else
if SUBPAGENAME == 'belge' then
iattn = not (targetText == BASEPAGENAME
or targetText == FULLPAGENAME)
else
iattn = not (targetText == FULLPAGENAME
or targetText == FULLPAGENAME .. '/belge')
end
end
end
if not (redirectTarget and redirectTarget.exists) then
iattn = true
end
end
end
if iattn then
item = '<span class="attentionseeking">' .. item .. '</span>'
end
output[#output + 1] = item .. '<br/>'
attn = attn or iattn
end
end
if not args.nocat then
if attn then
output[#output + 1] = '[[Kategorî:Daxwazên lênêrîna kurterêyan]]'
end
if SUBPAGENAME ~= 'belge' then
output[#output + 1] = '[[Kategorî:Rûpelên Wîkîferhengê bi kurterêyan]]'
end
end
output[#output + 1] = '</small></th></tr></table>'
return table.concat(output)
end
return export
1uwvkxui7vikn82bccwv75gdsj6gxwm
Bikarhêner:Wikihez/Gadget-linkify.js
2
974481
6291568
6276928
2026-07-26T15:02:59Z
Wikihez
7344
6291568
javascript
text/javascript
// MediaWiki gadget: automatically link selected text to existing wiktionary entries
(function($) {
var messages = {
ku: {
toolbarButtonName: "Li nivîsa bijartî bi otomatîkî wîkîlînk lê zêde bike",
delinkifyButtonName: "Ji nivîsa bijartî wîkîlînkan rake",
caseChangeLabel: "Nivîsa bijartî MEZIN/biçûk bike",
caseUpperLabel: "HEMÛ MEZIN",
caseLowerLabel: "hemû biçûk",
caseSentenceLabel: "Wekî cimleyan",
caseTitleLabel: "Herfa Ewil Mezin"
},
en: {
toolbarButtonName: "Automatically link selected text",
delinkifyButtonName: "Remove links from selected text",
caseChangeLabel: "Change letter case",
caseUpperLabel: "UPPERCASE",
caseLowerLabel: "lowercase",
caseSentenceLabel: "Sentence case",
caseTitleLabel: "Title Case"
},
es: {
toolbarButtonName: "Automáticamente insertar enlaces en la selección",
delinkifyButtonName: "Quitar enlaces del texto seleccionado",
caseChangeLabel: "Cambiar mayúsculas/minúsculas",
caseUpperLabel: "MAYÚSCULAS",
caseLowerLabel: "minúsculas",
caseSentenceLabel: "Case de oración",
caseTitleLabel: "Case de Título"
}
};
// Kurmanji function words that should never become links on their own,
// plus the 8 bare Kurmanji vowels alone.
var ignoredWordList = (
"a|an|ba|bê|bi|çi|çu|çû|da|de|dê|dî|di|e|ê|ên|erê|ev|ew|em|gel|ha|hê|her|" +
"hûn|i|î|jê|jî|ka|kî|ku|kû|lê|li|me|min|na|ne|o|pê|re|ra|ti|te|tê|" +
"tu|u|û|va|van|ve|vê|vî|wan|we|wê|wî|wir|vir|ya|yan|yê|yên"
).split("|");
var ignoredWords = {};
$.each(ignoredWordList, function(i, word) {
ignoredWords[word] = true;
});
/**
* One less than the maximum number of syllables to be considered as a compound
* word.
*/
var lookahead = 2;
// Token boundary: uppercase Latin A-Z, Kurmanji diacritic letters (Ç ê Î Ş Û),
// combining marks, and wiki markup delimiters.
var tokenRE = /(\s*(?:<!--[\s\S]+?-->|\{\{[\s\S]+?\}\}|\[\[.+?\]\]|[^A-ZÇÊÎŞÛçêîşû\u0300-\u036F{[< ]+|[{[<])\s*)/i;
// Matches the "{{ziman|ku}}" language-section template that marks an
// entry as actually having a Kurdish section. Templates are written by
// hand, so be forgiving of stray whitespace around the pipe/braces, but
// don't try to handle template redirects/aliases here.
var kuZimanRE = /\{\{\s*ziman\s*\|\s*ku\s*\}\}/i;
// "=== Formeke navdêrê ===" but also allow a numbered variant, e.g.
// "=== Formeke navdêrê 2 ===", which shows up when a page has more than
// one noun-form section.
var kuFormekeNavdereRE = /^\s*=+\s*Formeke navdêrê(?:\s+\d+)?\s*=+\s*$/im;
String.prototype.linkifyUncapitalize = function() {
return this[0].toLowerCase() + this.substr(1);
};
// Kurmanji noun-declension suffixes, longest first so the greedy match
// prefers the most specific ending. Generous rather than precise: every
// candidate root is verified against real wiktionary entries before use,
// so a bad strip is just discarded, never turned into a wrong link.
var nounSuffixes = [
"iyekî", "iyekê", "iyeke",
"yekî", "yekê", "yeke",
"ekî", "ekê", "eke",
"iyên", "iyan", "iyek", "iyê", "iya",
"yên", "yan", "yek", "yî", "yê", "ya",
"ek", "ên", "an",
"a", "ê", "î",
];
/**
* Given a lowercase surface word, returns candidate noun roots obtained by
* stripping a known declension suffix -- e.g. "rûpeleke" -> ["rûpel"].
* Longest remaining root first, so safer candidates are tried first.
*/
function getNounStemCandidates(word) {
var candidates = [];
for (var i = 0; i < nounSuffixes.length; i++) {
var suf = nounSuffixes[i];
// Require a 2+ character root, to avoid degenerate single letters.
if (word.length > suf.length + 1 && word.slice(-suf.length) === suf) {
candidates.push(word.slice(0, -suf.length));
}
}
// "giranî" + "yê" -> "giraniyê" drops the circumflex, so stripping "yê"
// gives "girani" not "giranî". Restore the î in place -- don't keep
// "girani" around too, it's invalid Kurdish orthography and would just
// be dead weight in the existence check.
candidates = candidates.map(function(root) {
if (root.length > 1 && root.slice(-1) === "i") {
return root.slice(0, -1) + "î";
}
return root;
});
candidates.sort(function(a, b) {
return b.length - a.length;
});
return candidates;
}
// Words this short are too ambiguous / noisy to bother linking, even if
// they aren't in ignoredWords and even if a real entry exists for them.
var MIN_LINKABLE_LENGTH = 3;
// MediaWiki batches titles up to 50 per request for normal users (500 for
// bots). Used both for the stem-confirmation content check and for the
// {{ziman|ku}} content check below.
var TITLE_BATCH_SIZE = 50;
/**
* Splits an array of titles into chunks of TITLE_BATCH_SIZE, so a single
* selection with a lot of unique words never trips the API's per-request
* titles limit.
*/
function chunkTitles(titles) {
var chunks = [];
for (var i = 0; i < titles.length; i += TITLE_BATCH_SIZE) {
chunks.push(titles.slice(i, i + TITLE_BATCH_SIZE));
}
return chunks;
}
var userLang = mw.config.get("wgUserLanguage");
function getMessage(name) {
return (messages[userLang] || messages.ku)[name] || messages.ku[name];
}
$.kuLinkify = function(text, callback, debug) {
var tokens = text.split(tokenRE);
var displayTokens = [];
var words = {};
var existingLinks = {};
// Surface words (lowercase-first-letter form) that have already been
// assigned a link during this run -- prevents the same word from being
// linked twice, even if it resolves to a different target (stem vs.
// direct entry vs. redlink) on a later occurrence.
var linkedWords = {};
// surface word -> candidate noun roots, e.g. stemCandidates["yekeya"] = ["yeke", "yekey"]
// Pruned by processStemCheckResults() below: kept if the surface word has
// no page of its own (nothing to contradict the stem), or if its page is
// confirmed as an inflected form (has "Formeke navdêrê"). Deleted if its
// page exists but ISN'T marked as a form -- that's a real headword, not
// something to stem.
var stemCandidates = {};
$.each(tokens, function(i, token) {
if (debug) displayTokens.push(mw.html.escape(token));
var tokenLinks = token.match(/\[\[.+?(?:\|.+?)?\]\]/g);
$.each(tokenLinks || [], function() {
var parts = this.match(/\[\[(.+?)(?:\|(.+?))?\]\]/);
var target = parts[1];
var display = parts[2] !== undefined ? parts[2] : target;
existingLinks[target] = true;
if (display) linkedWords[display.linkifyUncapitalize()] = true;
});
if (!token || token.match(tokenRE)) return;
var tokenWords = token.split(" ");
$.each(tokenWords, function(j, word) {
for (var k = 0; k < lookahead + 1 && tokenWords[j + k]; k++) {
if (k) word += " " + tokenWords[j + k];
if (!ignoredWords[word]) words[word] = true;
if (word[0] != word[0].toLowerCase()) {
var lowerWord = word.linkifyUncapitalize();
if (!ignoredWords[lowerWord]) {
words[lowerWord] = true;
}
}
}
});
// Collect noun-root candidates per single word (declension endings
// don't span multi-word compounds). Provisional until checked
// against page content below.
$.each(tokenWords, function(j, word) {
if (!word) return;
var lower = word.linkifyUncapitalize();
var stems = getNounStemCandidates(lower);
if (stems.length) {
stemCandidates[word] = stems;
}
});
if (debug) {
displayTokens[i] = "<b style='border: 1px solid gray;'>" + mw.html.escape(token) + "</b>";
}
});
if (debug) {
var list = "<pre id='linkify-tokens'>\n" + displayTokens.join("") + "\n</pre>";
$("#linkify-progress").html(list).dialog({
modal: true
});
}
mw.loader.using("mediawiki.api", function() {
var api = new mw.Api();
// Words that MATCHED a declension-suffix pattern need a content
// check before we trust the stem:
// - own page exists AND has "Formeke navdêrê" -> confirmed form, use stem
// - own page exists but NO "Formeke navdêrê" -> real headword, don't stem
// - own page doesn't exist at all -> nothing to check against,
// keep the stem candidate
// and let it be validated
// normally (does the STEM
// exist?) in the main pass
var stemCheckWords = [];
$.each(stemCandidates, function(word) {
stemCheckWords.push(word);
});
function processStemCheckResults(formPageExists, confirmedForms, then) {
$.each(stemCandidates, function(word) {
if (formPageExists[word] && !confirmedForms[word]) {
delete stemCandidates[word];
}
});
$.each(stemCandidates, function(word, stems) {
$.each(stems, function(s, stem) {
if (!ignoredWords[stem]) words[stem] = true;
});
});
then();
}
/**
* Given the set of page titles that exist (from prop=links
* "exists"), fetches their content and keeps only the ones that
* actually contain a {{ziman|ku}} section. A page "existing" only
* means SOME wiki has an entry there -- e.g. "away" exists as an
* English entry, so without this check "awayî" would get wrongly
* linked to [[away]] instead of stemmed to [[awa]]. Batched the
* same way as the stem-confirmation query above.
*/
function filterToKuEntries(existingTitles, then) {
var titles = [];
$.each(existingTitles, function(title) {
titles.push(title);
});
if (!titles.length) {
then({});
return;
}
var kuLinks = {};
var chunks = chunkTitles(titles);
var pending = chunks.length;
function handleChunk(chunk) {
api.get({
action: "query",
prop: "revisions",
rvprop: "content",
rvslots: "main",
titles: chunk.join("|"),
format: "json"
}).done(function(result) {
var normMap = {};
if (result.query && result.query.normalized) {
$.each(result.query.normalized, function(i, n) {
normMap[n.to] = n.from;
});
}
var pages = (result.query && result.query.pages) || {};
$.each(pages, function(pageId, page) {
if (!page.revisions || !page.revisions.length) return;
var originalTitle = normMap[page.title] || page.title;
var rev = page.revisions[0];
var content = (rev.slots && rev.slots.main && rev.slots.main["*"]) || rev["*"] || "";
if (kuZimanRE.test(content)) {
kuLinks[originalTitle] = true;
}
});
}).fail(function(error) {
// Content check failed for this chunk -- be
// conservative and don't link any of its titles,
// rather than risk linking to a non-Kurdish entry.
console.warn(error);
}).always(function() {
pending--;
if (pending === 0) then(kuLinks);
});
}
$.each(chunks, function(i, chunk) {
handleChunk(chunk);
});
}
function runLinkExistenceCheck() {
var uniqueWords = [];
$.each(words, function(word) {
if (!ignoredWords[word]) uniqueWords.push(word);
});
if (debug) {
var uniqueLinks = [];
$.each(existingLinks, function(link) {
uniqueLinks.push(link);
});
var stemDebug = [];
$.each(stemCandidates, function(word, stems) {
stemDebug.push(word + " -> " + stems.join(", "));
});
var list = "<hr /><p>Words to search for:</p><p>" + uniqueWords + "</p>";
list += "<hr /><p>Words that already have links:</p><p>" + uniqueLinks + "</p>";
list += "<hr /><p>Stem candidates kept:</p><p>" + stemDebug.join("<br>") + "</p>";
$("#linkify-progress").append(list);
}
if (!uniqueWords.length) return;
api.get({
action: "parse",
text: "[[" + uniqueWords.join("]] [[") + "]]",
prop: "links"
}).done(function(results) {
$("#linkify-progress").append("<hr />");
var displayLinks = [];
var existsAnywhere = {};
$.each(results.parse.links, function() {
if (debug && "exists" in this) displayLinks.push(this["*"]);
if ("exists" in this) existsAnywhere[this["*"]] = true;
});
if (debug) {
$("#linkify-progress").append("<p>Existing entries (any language):</p><p>" + displayLinks + "</p>");
}
// A page existing isn't enough -- confirm it actually has
// a {{ziman|ku}} section before we're willing to link to
// it. `links` from here on means "exists AND is Kurdish".
filterToKuEntries(existsAnywhere, function(links) {
if (debug) {
var kuDisplayLinks = [];
$.each(links, function(title) {
kuDisplayLinks.push(title);
});
$("#linkify-progress").append("<p>Confirmed {{ziman|ku}} entries:</p><p>" + kuDisplayLinks + "</p>");
}
$.each(tokens, function(i, token) {
if (!token || token.match(tokenRE)) return;
var tokenWords = token.split(" ");
for (var j = 0; j < tokenWords.length; j++) {
if (!tokenWords[j]) continue;
var word = [];
for (var k = 0; k < lookahead + 1; k++) {
word.push(tokenWords[j + k]);
}
for (var k = lookahead; k >= 0; k--) {
var compoundWord = word.join(" ");
if (k === 0) {
var lowerSingle = compoundWord.linkifyUncapitalize();
// Never link words this short, even if a real
// entry exists for them.
if (compoundWord.length < MIN_LINKABLE_LENGTH) break;
// Only link the first occurrence of a given
// surface word; skip repeats.
if (linkedWords[lowerSingle]) break;
var stems = stemCandidates[compoundWord] || stemCandidates[lowerSingle] || [];
// 1. Confirmed form, or no own page -- try the stem.
var stemLinked = false;
for (var s = 0; s < stems.length; s++) {
var stem = stems[s];
if ((stem in links) && !(stem in existingLinks) && !ignoredWords[stem]) {
existingLinks[stem] = true;
linkedWords[lowerSingle] = true;
tokenWords[j] = "[[" + stem + "|" + tokenWords[j] + "]]";
stemLinked = true;
break;
}
}
if (stemLinked) break;
if (!ignoredWords[compoundWord] && !ignoredWords[lowerSingle]) {
// 2. Exact word (or lowercase form) is itself a real entry.
var directTarget = null;
if (compoundWord in links) {
directTarget = compoundWord;
} else if (lowerSingle in links) {
directTarget = lowerSingle;
}
if (directTarget && !(directTarget in existingLinks)) {
existingLinks[directTarget] = true;
linkedWords[lowerSingle] = true;
tokenWords[j] = "[[" + directTarget + "|" + tokenWords[j] + "]]";
} else if (!directTarget && !(compoundWord in existsAnywhere) && !(compoundWord in existingLinks)) {
// 3. Doesn't exist anywhere yet -- self-link as a
// redlink so it shows up as a page to create,
// e.g. [[peyva|peyva]]. If it exists but only in
// a non-Kurdish language, don't self-link either;
// that would create a redlink over a page that's
// actually occupied by another language's entry.
existingLinks[compoundWord] = true;
linkedWords[lowerSingle] = true;
tokenWords[j] = "[[" + compoundWord + "|" + tokenWords[j] + "]]";
}
}
break;
}
if (compoundWord in links) {
if ((compoundWord in existingLinks) || ignoredWords[compoundWord]) {
j += k;
break;
}
existingLinks[compoundWord] = true;
linkedWords[compoundWord.linkifyUncapitalize()] = true;
tokenWords[j] = "[[" + tokenWords[j];
tokenWords[j + k] += "]]";
j += k;
break;
}
var lowerWord = compoundWord.linkifyUncapitalize();
if (lowerWord in links) {
if ((lowerWord in existingLinks) || ignoredWords[lowerWord]) {
j += k;
break;
}
existingLinks[lowerWord] = true;
linkedWords[lowerWord] = true;
tokenWords[j] = "[[" + lowerWord + "|" + tokenWords[j];
tokenWords[j + k] += "]]";
j += k;
break;
}
word.splice(-1, 1);
}
}
tokens[i] = tokenWords.join(" ");
if (debug) {
displayTokens[i] = "<b style='border: 1px solid gray;'>" + mw.html.escape(tokens[i]) + "</b>";
}
});
if (debug) {
$("#linkify-tokens").html("\n" + displayTokens.join("") + "\n");
}
callback(tokens.join(""));
}); // end filterToKuEntries
}).fail(function(error) {
console.warn(error);
callback();
});
}
if (!stemCheckWords.length) {
processStemCheckResults({}, {}, runLinkExistenceCheck);
return;
}
// NOTE: MediaWiki batches titles up to 50 per request for normal
// users (500 for bots). Chunk stemCheckWords here if it ever grows
// past that for a single selection.
api.get({
action: "query",
prop: "revisions",
rvprop: "content",
rvslots: "main",
titles: stemCheckWords.join("|"),
format: "json"
}).done(function(result) {
var confirmedForms = {};
var formPageExists = {};
var normMap = {};
if (result.query && result.query.normalized) {
$.each(result.query.normalized, function(i, n) {
normMap[n.to] = n.from;
});
}
var pages = (result.query && result.query.pages) || {};
$.each(pages, function(pageId, page) {
var originalWord = normMap[page.title] || page.title;
if (!page.revisions || !page.revisions.length) {
formPageExists[originalWord] = false;
return;
}
formPageExists[originalWord] = true;
var rev = page.revisions[0];
var content = (rev.slots && rev.slots.main && rev.slots.main["*"]) || rev["*"] || "";
if (kuFormekeNavdereRE.test(content)) {
confirmedForms[originalWord] = true;
}
});
processStemCheckResults(formPageExists, confirmedForms, runLinkExistenceCheck);
}).fail(function() {
// Content check failed -- be conservative: treat every candidate
// as if its page exists but isn't confirmed, so stemming is
// skipped rather than risking a wrong link.
var formPageExists = {};
$.each(stemCandidates, function(word) {
formPageExists[word] = true;
});
processStemCheckResults(formPageExists, {}, runLinkExistenceCheck);
});
});
}
// Matches a single wikilink: [[target]] or [[target|display]] (also
// File:/Category: links, unwrapped the same way).
var wikilinkRE = /\[\[([^\]|]*)\|([^\]]*)\]\]|\[\[([^\]|]*)\]\]/g;
/**
* Strips wikilink markup, keeping only the reader-facing text.
* [[target|display]] -> display, [[target]] -> target.
*/
$.kuDelinkify = function(text) {
return text.replace(wikilinkRE, function(match, pipedTarget, pipedDisplay, bareTarget) {
return pipedDisplay !== undefined ? pipedDisplay : bareTarget;
});
};
// ---------------------------------------------------------------
// Letter-case conversion (UPPERCASE / lowercase / Sentence case /
// Title Case). Kurmanji-only, so plain JS toUpperCase()/toLowerCase()
// already do the right thing. Title Case reuses ignoredWords as the
// stopword list for small grammatical words.
// ---------------------------------------------------------------
var KU_LETTER_CLASS = "a-zA-ZçêîşûÇÊÎŞÛ";
var kuNonLetterLeadingRE = new RegExp("^[^" + KU_LETTER_CLASS + "]*");
var kuNonLetterTrailingRE = new RegExp("[^" + KU_LETTER_CLASS + "]*$");
var kuSentenceBreakRE = new RegExp(
"(^\\s*[a-zçêîşû]|[.!?][\"'\\)\\]]*\\s+[a-zçêîşû])", "gi"
);
function kuCapitalizeFirstLetter(word) {
if (word === "") return word;
var lowered = word.toLowerCase();
return lowered.charAt(0).toUpperCase() + lowered.slice(1);
}
function kuToSentenceCase(text) {
var lowered = text.toLowerCase();
return lowered.replace(kuSentenceBreakRE, function(match) {
var idx = match.length - 1;
return match.slice(0, idx) + match.charAt(idx).toUpperCase();
});
}
// Capitalizes one word chunk: handles leading/trailing punctuation,
// hyphenated compounds, and keeps ignoredWords lowercase unless forced
// (first word, or right after an opening bracket/quote).
function kuCapitalizeWordChunk(chunk, forceCapitalize) {
var leading = (chunk.match(kuNonLetterLeadingRE) || [""])[0];
var trailing = (chunk.match(kuNonLetterTrailingRE) || [""])[0];
var core = chunk.slice(leading.length, chunk.length - trailing.length);
if (core === "") return chunk;
if (/[(["'‘“]/.test(leading)) forceCapitalize = true;
var loweredCore = core.toLowerCase();
var isStopWord = !forceCapitalize && ignoredWords[loweredCore];
var newCore;
if (isStopWord) {
newCore = loweredCore;
} else {
newCore = core.split("-").map(function(part) {
return kuCapitalizeFirstLetter(part);
}).join("-");
}
return leading + newCore + trailing;
}
function kuToTitleCase(text) {
var wordIndex = 0;
return text.split(/(\s+)/).map(function(chunk) {
if (chunk === "" || /^\s+$/.test(chunk)) return chunk;
var isFirstWord = wordIndex === 0;
wordIndex++;
return kuCapitalizeWordChunk(chunk, isFirstWord);
}).join("");
}
$.kuApplyCase = function(text, form) {
switch (form) {
case "upper":
return text.toUpperCase();
case "lower":
return text.toLowerCase();
case "sentence":
return kuToSentenceCase(text);
case "title":
return kuToTitleCase(text);
default:
return text;
}
};
// ---------------------------------------------------------------
// Dark-mode-aware toolbar icons. Vector 2022's day/night/os switch
// marks its active theme with a class on <html>
// (skin-theme-clientpref-{day,night,os}); when that class says "os",
// or isn't present at all (older skins, no theme switcher), fall back
// to the browser's own prefers-color-scheme signal.
// ---------------------------------------------------------------
var kuIcons = {
wikiText: {
normal: "//upload.wikimedia.org/wikipedia/commons/a/a0/OOjs_UI_icon_wikiText.svg",
invert: "//upload.wikimedia.org/wikipedia/commons/f/fb/OOjs_UI_icon_wikiText-invert.svg"
},
noWikiText: {
normal: "//upload.wikimedia.org/wikipedia/commons/8/8c/OOjs_UI_icon_noWikiText-rtl.svg",
invert: "//upload.wikimedia.org/wikipedia/commons/4/44/OOjs_UI_icon_noWikiText-rtl-invert.svg"
},
searchCaseSensitive: {
normal: "//upload.wikimedia.org/wikipedia/commons/e/e5/OOjs_UI_icon_searchCaseSensitive.svg",
invert: "//upload.wikimedia.org/wikipedia/commons/0/02/OOjs_UI_icon_searchCaseSensitive-invert.svg"
}
};
function kuIsDarkMode() {
var $html = $(document.documentElement);
if ($html.hasClass("skin-theme-clientpref-night")) return true;
if ($html.hasClass("skin-theme-clientpref-day")) return false;
return !!(window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches);
}
function kuGetIcon(name) {
var pair = kuIcons[name];
return kuIsDarkMode() ? pair.invert : pair.normal;
}
function getLinkifyTool() {
return {
type: "button",
action: {
type: "callback",
execute: function() {
mw.loader.using("jquery.textSelection", function() {
var selection = $("#wpTextbox1").textSelection("getSelection");
$.kuLinkify(selection, function(linkedText) {
if (!linkedText) return;
$("#wpTextbox1").textSelection("encapsulateSelection", {
peri: linkedText,
replace: true
});
}, $.kuLinkify_debug);
});
}
},
label: getMessage("toolbarButtonName"),
icon: kuGetIcon("wikiText")
};
}
function getDelinkifyTool() {
return {
type: "button",
action: {
type: "callback",
execute: function() {
mw.loader.using("jquery.textSelection", function() {
var selection = $("#wpTextbox1").textSelection("getSelection");
var plainText = $.kuDelinkify(selection);
if (plainText === selection) return;
$("#wpTextbox1").textSelection("encapsulateSelection", {
peri: plainText,
replace: true
});
});
}
},
label: getMessage("delinkifyButtonName"),
icon: kuGetIcon("noWikiText")
};
}
// Applies a case transform to the current textbox selection in place.
function applyCaseToSelection(form) {
mw.loader.using("jquery.textSelection", function() {
var selection = $("#wpTextbox1").textSelection("getSelection");
if (!selection) return;
var converted = $.kuApplyCase(selection, form);
if (converted === selection) return;
$("#wpTextbox1").textSelection("encapsulateSelection", {
peri: converted,
replace: true
});
});
}
// Hand-built dropdown (wikiEditor "element" tool) instead of the built-in
// "select" type, since that one's close-on-select/re-click behavior isn't
// reliably patchable from a gadget. Reuses wikiEditor's own CSS classes so
// it looks like a native dropdown.
function buildCaseChangeElement() {
var forms = [{
key: "upper",
label: getMessage("caseUpperLabel")
},
{
key: "lower",
label: getMessage("caseLowerLabel")
},
{
key: "sentence",
label: getMessage("caseSentenceLabel")
},
{
key: "title",
label: getMessage("caseTitleLabel")
}
];
var $select = $("<div>").addClass("tool tool-select").attr("rel", "caseChange");
var $options = $("<div>").addClass("options");
var $menu = $("<div>").addClass("menu").append($options).hide();
function closeMenu() {
$select.removeClass("options-shown");
$menu.hide();
$label.attr("aria-expanded", "false");
}
function openMenu() {
// Only one such dropdown open at a time.
$(".tool-select").not($select).removeClass("options-shown").find(".menu").hide();
$select.addClass("options-shown");
$menu.show();
$label.attr("aria-expanded", "true");
}
$.each(forms, function(i, form) {
$("<a>")
.addClass("option")
.attr({
rel: form.key,
tabindex: 0,
role: "menuitem"
})
.text(form.label)
.on("mousedown", function(e) {
e.preventDefault();
return false;
})
.on("click keydown", function(e) {
if (e.type === "click" || (e.type === "keydown" && e.key === "Enter")) {
applyCaseToSelection(form.key);
closeMenu();
e.preventDefault();
return false;
}
})
.appendTo($options);
});
var $labelIcon = $("<img>")
.addClass("oo-ui-image")
.attr({
src: kuGetIcon("searchCaseSensitive"),
alt: ""
});
var $label = $("<a>")
.addClass("label")
.attr({
role: "button",
tabindex: 0,
"aria-expanded": "false",
"aria-haspopup": "menu",
title: getMessage("caseChangeLabel")
})
.css({
display: "inline-flex",
"align-items": "center",
gap: "4px"
})
.append($labelIcon, $("<span>").text("Mezin/biçûk"))
.on("mousedown", function(e) {
e.preventDefault();
return false;
})
.on("click keydown", function(e) {
if (e.type === "click" || (e.type === "keydown" && e.key === "Enter")) {
if ($select.hasClass("options-shown")) {
closeMenu();
} else {
openMenu();
}
e.preventDefault();
return false;
}
});
$select.append($label, $menu);
// Close on any click elsewhere. Re-bound via .off() first so
// re-registering this tool never stacks up duplicate handlers.
$(document).off("mousedown.kuCaseChange").on("mousedown.kuCaseChange", function(e) {
if (!$(e.target).closest($select).length) {
closeMenu();
}
});
return $select;
}
function getCaseChangeTool() {
return {
type: "element",
element: buildCaseChangeElement
};
}
function addLinkifyButton() {
var $textbox = $("#wpTextbox1");
// No edit form on this page (VisualEditor, view/history page, etc.).
if (!$textbox.length) return;
if ($.kuLinkify) {
$textbox.after($(mw.html.element("div", {
id: "linkify-progress",
title: getMessage("toolbarButtonName")
})).hide());
}
// Single source of truth so both registration paths below never
// build duplicate tool objects.
function buildTools() {
return {
linkify: getLinkifyTool(),
delinkify: getDelinkifyTool(),
caseChange: getCaseChangeTool()
};
}
// Guards against adding the tools twice: the buildSection event and
// the mw.loader.using callback below can both fire on the same load.
var toolsAdded = false;
// Rebuilds the kutools toolbar group so its icons pick up whichever
// light/dark icon variant matches the theme active right now --
// used when the day/night/os toggle or the OS-level color scheme
// changes without a page reload.
function rebuildIcons() {
var context = $textbox.data("wikiEditor-context");
if (!context || !context.modules || !context.modules.toolbar) return;
$textbox.wikiEditor("removeFromToolbar", {
section: "main",
group: "kutools"
});
$textbox.wikiEditor("addToToolbar", {
section: "main",
groups: {
kutools: {
tools: buildTools()
}
}
});
}
// Bind synchronously (not inside mw.loader.using's callback) so we
// don't miss the event if ext.wikiEditor resolves instantly/cached.
$textbox.on('wikiEditor-toolbar-buildSection-main', function(evt, section) {
section.groups.kutools = {
tools: buildTools()
};
toolsAdded = true;
});
mw.loader.using("ext.wikiEditor", function () {
if (toolsAdded) return;
// Fallback: toolbar was already built (and the event already fired)
// before this script ran -- add tools directly via the public API.
var context = $textbox.data("wikiEditor-context");
if (context && context.modules && context.modules.toolbar) {
$textbox.wikiEditor("addToToolbar", {
section: "main",
groups: {
kutools: {
tools: buildTools()
}
}
});
toolsAdded = true;
}
});
// Watch for the OS-level color scheme changing (system dark mode
// toggled while the page is open).
if (window.matchMedia) {
var kuDarkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");
var kuOnSchemeChange = function() {
if (!toolsAdded) return;
rebuildIcons();
};
if (kuDarkModeQuery.addEventListener) {
kuDarkModeQuery.addEventListener("change", kuOnSchemeChange);
} else if (kuDarkModeQuery.addListener) {
// Older browsers.
kuDarkModeQuery.addListener(kuOnSchemeChange);
}
}
// Watch for Vector 2022's day/night/os toggle, which flips a class
// on <html> without reloading the page.
if (window.MutationObserver) {
var kuThemeObserver = new MutationObserver(function() {
if (!toolsAdded) return;
rebuildIcons();
});
kuThemeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"]
});
}
}
// Only run on edit pages -- avoids a wasted mw.loader.using round trip
// elsewhere, where #wpTextbox1 never exists anyway.
if ($.inArray(mw.config.get("wgAction"), ["edit", "submit"]) !== -1) {
addLinkifyButton();
}
})(jQuery);
6719abrhn69xqi4pj1a8u5y4wrbxzn4
Bikarhêner:Wikihez/Gadget-start.js
2
974484
6291577
6291556
2026-07-26T17:08:30Z
Wikihez
7344
6291577
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
function wrapNativeSelect(select) {
var placeholder = select.attr("data-placeholder") || "Lê bigere…";
// Hide the native select — we drive it programmatically.
select.hide();
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results'></ul>").hide();
// Full option cache — rebuilt when chosen:updated fires.
var allOptions = [];
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
allOptions.push({
value: v,
text: $(this).text(),
disabled: $(this).prop("disabled")
});
});
}
cacheOptions();
function showResults(query) {
var q = query.trim().toLowerCase();
resultsList.empty();
var matched;
if (q) {
matched = [];
$.each(allOptions, function (_, o) {
if (o.disabled) return;
var codeMatch = o.value.toLowerCase().indexOf(q) !== -1;
var nameMatch = o.text.toLowerCase().indexOf(q) !== -1;
if (codeMatch || nameMatch) {
matched.push({ opt: o, codeMatch: codeMatch });
}
});
// Code matches (especially prefix matches) come first.
matched.sort(function (a, b) {
var aCode = a.opt.value.toLowerCase();
var bCode = b.opt.value.toLowerCase();
var aPrefix = aCode.indexOf(q) === 0;
var bPrefix = bCode.indexOf(q) === 0;
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
if (a.codeMatch !== b.codeMatch) return a.codeMatch ? -1 : 1;
return 0;
});
matched = matched.map(function (item) { return item.opt; });
} else {
matched = allOptions.filter(function (o) { return !o.disabled; });
}
if (!matched.length) {
resultsList.hide();
return;
}
$.each(matched.slice(0, 50), function (_, opt) {
var li = $("<li></li>").text(opt.text).attr("data-value", opt.value);
// 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);
});
// Keep mousedown for desktop fallback.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
resultsList.append(li);
});
// Position the list above or below depending on available 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%"
});
resultsList.show();
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
resultsList.hide();
select.trigger("change");
}
textInput.on("input focus", function () {
showResults($(this).val());
});
textInput.on("blur", function () {
// Short delay so a touchstart on a result item fires first.
setTimeout(function () { resultsList.hide(); }, 200);
});
// chosen:updated → re-cache (called after ziman.json loads).
select.on("chosen:updated", function () {
cacheOptions();
// If a value is already selected, keep the display text in sync.
var cur = select.val();
if (cur) {
var match = $.grep(allOptions, function (o) { return o.value === cur; })[0];
if (match) textInput.val(match.text);
}
});
// No-op .chosen() so desktop call-sites don't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c[0]] = 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// 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 (_, 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(tpl.name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (_, 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(tpl.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [[code, genderLabel(code)]];
});
populateMenus(select, choices);
if ($.grep(choices, function (c) { return c[0] === prevVal; }).length) {
select.val(prevVal);
}
if (!isMobile) select.trigger("chosen:updated");
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
choices: [
["gh", "gerguhêz"],
['x-gh', 'xwerû-gerguhêz'],
['p-gh', 'pêkhatî-gerguhêz'],
['h-gh', 'hevedudanî-gerguhêz'],
['b-gh', 'biwêjî-gerguhêz'],
["ngh", "negerguhêz"],
['x-ngh', 'xwerû-negerguhêz'],
['p-ngh', 'pêkhatî-negerguhêz'],
['h-ngh', 'hevedudanî-negerguhêz'],
['b-ngh', 'biwêjî-negerguhêz'] ],
output: function () { return ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (_, tpl) {
var opt = $("<option></option>")
.val(tpl.name)
.text(tpl.name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
if (!isMobile) tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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");
srcSel.trigger("chosen:updated");
}
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");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (_, f) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(f.label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", f.key)
.attr("placeholder", f.label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Apply Chosen to the new block's template selector on desktop.
if (!isMobile) newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: [],
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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 array.
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 (_, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(tpl.name + " — " + tpl.desc));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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", "Nimûne: 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (_, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(tpl.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("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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" }));
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);
}
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);
});
});
},
/** 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);
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 "";
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) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || [];
var bLang = langsByCode[b[0]] || [];
return (aLang[2] || "").localeCompare(bLang[2] || "", "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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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",
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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
// 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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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[2] || "").localeCompare(bLang[2] || "", "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 ên (ne jêgirtinan) 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));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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 jêrê 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
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.");
});
}
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "\n* {{" + name + "}}");
$(this).val("");
if (!isMobile) sel.trigger("chosen:updated");
ta.focus();
});
wrap.append(sel).append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify chosen widgets of the programmatic value change.
if (!isMobile) fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>"));
$.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[1]);
if (choice[2] !== undefined) {
// This is a language choice [code, name, sortKey] — show code.
displayText = capitalize(choice[1]) + " (" + choice[0] + ")";
}
var option = $(mw.html.element("option", {
value: choice[0] }, displayText));
if (disabledValues[choice[0]]) 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);
if (isMobile) {
populateMenus(input, step.keys, getDisabledKeys(step));
// On mobile return the wrapper; the select is inside it.
// Change events still fire on the inner select.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
}
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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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[1] : 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
if (isMobile) {
stepDiv.append(wrapNativeSelect(input));
} else {
stepDiv.append(input);
}
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
if (!isMobile) stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
if (!isMobile) $("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
framed: false
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
if (!isMobile) startForm.find("select").chosen(chosenOptions);
refresh();
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Pinned choices come from MediaWiki:Gadget-start.js/data.json (langChoices),
// already loaded into steps.lang.choices before build() runs.
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice[0]; });
zimanJsonPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = [code, name, sortKey];
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey (index 2).
if (choice.length < 3) choice[2] = choice[1];
langsByCode[choice[0]] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a[2].localeCompare(b[2], userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
var langMenu = $("#wiktku-start-lang-field");
populateMenus(langMenu, steps.lang.choices);
langMenu.trigger("chosen:updated");
var transMenus = $("#wiktku-start-trans .wiktku-start-step-keyfield");
populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
transMenus.trigger("chosen:updated");
var dundeMenus = $("#wiktku-start-dunde .wiktku-start-step-keyfield");
populateMenus(dundeMenus, steps.dunde.keys, getDisabledKeys(steps.dunde));
dundeMenus.trigger("chosen:updated");
var zaravaMenus = $("#wiktku-start-zarava .wiktku-start-step-keyfield");
populateMenus(zaravaMenus, steps.zarava.keys, getDisabledKeys(steps.zarava));
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page (if the "Li
// cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
});
}
// jquery.chosen/oojs-ui-core load in parallel with metaApiPromise above
// (mediawiki.api + the two API requests) — this block only needs to wait
// on the UI modules itself.
mw.loader.using(["jquery.chosen", "oojs-ui-core"], function () {
if (!isMobile) {
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
}
// Load metadata, langChoices, posChoices and tplDefs from the companion
// data page so they can be updated without touching this script.
// (Request was already fired via metaApiPromise above, in parallel
// with this module load — we're just picking up the result here.)
metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Populate lang choices (pinned list used by build() → ziman.json loader).
if (d.langChoices) steps.lang.choices = d.langChoices;
// Populate POS choices.
if (d.posChoices) steps.pos.choices = d.posChoices;
// 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.dfn.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 (now populated) lang choices.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
}).always(build);
});
});
//</nowiki>
2dpgy94jef0yyeungjul1j7njo4ndty
6291578
6291577
2026-07-26T17:18:33Z
Wikihez
7344
6291578
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
// Computed early (no async dependency) so the "starting up" notice below can
// fire immediately, rather than waiting on any module load.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
if (!isMobile) {
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
}
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
function wrapNativeSelect(select) {
var placeholder = select.attr("data-placeholder") || "Lê bigere…";
// Hide the native select — we drive it programmatically.
select.hide();
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results'></ul>").hide();
// Full option cache — rebuilt when chosen:updated fires.
var allOptions = [];
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
allOptions.push({
value: v,
text: $(this).text(),
disabled: $(this).prop("disabled")
});
});
}
cacheOptions();
function showResults(query) {
var q = query.trim().toLowerCase();
resultsList.empty();
var matched;
if (q) {
matched = [];
$.each(allOptions, function (_, o) {
if (o.disabled) return;
var codeMatch = o.value.toLowerCase().indexOf(q) !== -1;
var nameMatch = o.text.toLowerCase().indexOf(q) !== -1;
if (codeMatch || nameMatch) {
matched.push({ opt: o, codeMatch: codeMatch });
}
});
// Code matches (especially prefix matches) come first.
matched.sort(function (a, b) {
var aCode = a.opt.value.toLowerCase();
var bCode = b.opt.value.toLowerCase();
var aPrefix = aCode.indexOf(q) === 0;
var bPrefix = bCode.indexOf(q) === 0;
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
if (a.codeMatch !== b.codeMatch) return a.codeMatch ? -1 : 1;
return 0;
});
matched = matched.map(function (item) { return item.opt; });
} else {
matched = allOptions.filter(function (o) { return !o.disabled; });
}
if (!matched.length) {
resultsList.hide();
return;
}
$.each(matched.slice(0, 50), function (_, opt) {
var li = $("<li></li>").text(opt.text).attr("data-value", opt.value);
// 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);
});
// Keep mousedown for desktop fallback.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
resultsList.append(li);
});
// Position the list above or below depending on available 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%"
});
resultsList.show();
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
resultsList.hide();
select.trigger("change");
}
textInput.on("input focus", function () {
showResults($(this).val());
});
textInput.on("blur", function () {
// Short delay so a touchstart on a result item fires first.
setTimeout(function () { resultsList.hide(); }, 200);
});
// chosen:updated → re-cache (called after ziman.json loads).
select.on("chosen:updated", function () {
cacheOptions();
// If a value is already selected, keep the display text in sync.
var cur = select.val();
if (cur) {
var match = $.grep(allOptions, function (o) { return o.value === cur; })[0];
if (match) textInput.val(match.text);
}
});
// No-op .chosen() so desktop call-sites don't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c[0]] = 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// 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 (_, 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(tpl.name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (_, 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(tpl.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [[code, genderLabel(code)]];
});
populateMenus(select, choices);
if ($.grep(choices, function (c) { return c[0] === prevVal; }).length) {
select.val(prevVal);
}
if (!isMobile) select.trigger("chosen:updated");
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
choices: [
["gh", "gerguhêz"],
['x-gh', 'xwerû-gerguhêz'],
['p-gh', 'pêkhatî-gerguhêz'],
['h-gh', 'hevedudanî-gerguhêz'],
['b-gh', 'biwêjî-gerguhêz'],
["ngh", "negerguhêz"],
['x-ngh', 'xwerû-negerguhêz'],
['p-ngh', 'pêkhatî-negerguhêz'],
['h-ngh', 'hevedudanî-negerguhêz'],
['b-ngh', 'biwêjî-negerguhêz'] ],
output: function () { return ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (_, tpl) {
var opt = $("<option></option>")
.val(tpl.name)
.text(tpl.name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
if (!isMobile) tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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");
srcSel.trigger("chosen:updated");
}
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");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (_, f) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(f.label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", f.key)
.attr("placeholder", f.label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Apply Chosen to the new block's template selector on desktop.
if (!isMobile) newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: [],
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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 array.
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 (_, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(tpl.name + " — " + tpl.desc));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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", "Nimûne: 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (_, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(tpl.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("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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" }));
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);
}
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);
});
});
},
/** 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);
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 "";
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) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || [];
var bLang = langsByCode[b[0]] || [];
return (aLang[2] || "").localeCompare(bLang[2] || "", "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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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",
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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
// 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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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[2] || "").localeCompare(bLang[2] || "", "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 ên (ne jêgirtinan) 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));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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 jêrê 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
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.");
});
}
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "\n* {{" + name + "}}");
$(this).val("");
if (!isMobile) sel.trigger("chosen:updated");
ta.focus();
});
wrap.append(sel).append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify chosen widgets of the programmatic value change.
if (!isMobile) fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>"));
$.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[1]);
if (choice[2] !== undefined) {
// This is a language choice [code, name, sortKey] — show code.
displayText = capitalize(choice[1]) + " (" + choice[0] + ")";
}
var option = $(mw.html.element("option", {
value: choice[0] }, displayText));
if (disabledValues[choice[0]]) 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);
if (isMobile) {
populateMenus(input, step.keys, getDisabledKeys(step));
// On mobile return the wrapper; the select is inside it.
// Change events still fire on the inner select.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
}
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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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[1] : 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
if (isMobile) {
stepDiv.append(wrapNativeSelect(input));
} else {
stepDiv.append(input);
}
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
if (!isMobile) stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
if (!isMobile) $("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
framed: false
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
if (!isMobile) startForm.find("select").chosen(chosenOptions);
refresh();
// The language list (pinned + full ziman.json list, merged) is already
// finalized in steps.lang.choices/steps.trans.keys/etc. before build()
// runs, so every select above was already created with its full option
// list — no repopulation needed here.
// Silently restore any draft the editor previously saved for this page
// (if the "Li cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// jquery.chosen/oojs-ui-core load in parallel with metaApiPromise above
// (mediawiki.api + the two API requests) — nothing here depends on them
// finishing first.
var uiModulesPromise = mw.loader.using(["jquery.chosen", "oojs-ui-core"]);
// Process data.json: metadata, gender labels, POS choices, template defs,
// and the pinned language list (steps.lang.choices).
var dataProcessedPromise = metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
if (d.metadata) $.extend(metadata, d.metadata);
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Pinned list, used below to merge with the full ziman.json list.
if (d.langChoices) steps.lang.choices = d.langChoices;
if (d.posChoices) steps.pos.choices = d.posChoices;
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
if (d.posExamples) steps.dfn.posExamples = d.posExamples;
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
});
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Chained after dataProcessedPromise (needs the pinned list above to know
// which codes to skip) and merged into steps.*.choices/keys before build()
// ever runs, so every select is created with its full, final option list —
// no separate repopulation step once the form is already on screen.
var langsReadyPromise = dataProcessedPromise.then(function () {
return zimanJsonPromise;
}).then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice[0]; });
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = [code, name, sortKey];
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey (index 2).
if (choice.length < 3) choice[2] = choice[1];
langsByCode[choice[0]] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a[2].localeCompare(b[2], userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
});
// Only build (and show) the form once the UI modules are loaded AND the
// full, merged language list is ready — so the buttons and language
// dropdowns appear complete on first paint instead of popping in a moment
// after the rest of the form.
$.when(uiModulesPromise, langsReadyPromise).always(build);
});
//</nowiki>
mssqyqxxdwz7t1vyma0jeznw20b9ic4
6291580
6291578
2026-07-26T17:27:48Z
Wikihez
7344
6291580
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
// Computed early (no async dependency) so the "starting up" notice below can
// fire immediately, rather than waiting on any module load.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
if (!isMobile) {
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
}
// Kick off the ziman.json request as early as possible — the language
// dropdown only needs this plus the in-script PINNED_LANG_CHOICES list
// (defined below), not data.json. Needs the mediawiki.api module; that
// module is used all over the wiki so this resolves near-instantly in
// practice, but we still wait for it properly rather than assuming
// mw.Api is already defined. data.json is fetched separately, only once
// the form itself has been built (see loadDataJson() further down) —
// nothing in the initial render needs it.
var zimanJsonPromise;
var mediawikiApiReadyPromise = mw.loader.using("mediawiki.api").then(function () {
zimanJsonPromise = new mw.Api().get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return zimanJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
function wrapNativeSelect(select) {
var placeholder = select.attr("data-placeholder") || "Lê bigere…";
// Hide the native select — we drive it programmatically.
select.hide();
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results'></ul>").hide();
// Full option cache — rebuilt when chosen:updated fires.
var allOptions = [];
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
allOptions.push({
value: v,
text: $(this).text(),
disabled: $(this).prop("disabled")
});
});
}
cacheOptions();
function showResults(query) {
var q = query.trim().toLowerCase();
resultsList.empty();
var matched;
if (q) {
matched = [];
$.each(allOptions, function (_, o) {
if (o.disabled) return;
var codeMatch = o.value.toLowerCase().indexOf(q) !== -1;
var nameMatch = o.text.toLowerCase().indexOf(q) !== -1;
if (codeMatch || nameMatch) {
matched.push({ opt: o, codeMatch: codeMatch });
}
});
// Code matches (especially prefix matches) come first.
matched.sort(function (a, b) {
var aCode = a.opt.value.toLowerCase();
var bCode = b.opt.value.toLowerCase();
var aPrefix = aCode.indexOf(q) === 0;
var bPrefix = bCode.indexOf(q) === 0;
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
if (a.codeMatch !== b.codeMatch) return a.codeMatch ? -1 : 1;
return 0;
});
matched = matched.map(function (item) { return item.opt; });
} else {
matched = allOptions.filter(function (o) { return !o.disabled; });
}
if (!matched.length) {
resultsList.hide();
return;
}
$.each(matched.slice(0, 50), function (_, opt) {
var li = $("<li></li>").text(opt.text).attr("data-value", opt.value);
// 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);
});
// Keep mousedown for desktop fallback.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
resultsList.append(li);
});
// Position the list above or below depending on available 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%"
});
resultsList.show();
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
resultsList.hide();
select.trigger("change");
}
textInput.on("input focus", function () {
showResults($(this).val());
});
textInput.on("blur", function () {
// Short delay so a touchstart on a result item fires first.
setTimeout(function () { resultsList.hide(); }, 200);
});
// chosen:updated → re-cache (called after ziman.json loads).
select.on("chosen:updated", function () {
cacheOptions();
// If a value is already selected, keep the display text in sync.
var cur = select.val();
if (cur) {
var match = $.grep(allOptions, function (o) { return o.value === cur; })[0];
if (match) textInput.val(match.text);
}
});
// No-op .chosen() so desktop call-sites don't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
var langsByCode = {};
// Pinned languages, shown first in the "ziman" (language) dropdown ahead
// of the full alphabetical list loaded from ziman.json. Kept in the
// script (rather than data.json) since the language dropdown doesn't
// need anything else from data.json to build. Order here is preserved
// (insertion order of string keys), so keep the most relevant languages
// first.
var PINNED_LANG_CHOICES = {
"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î"
};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c[0]] = 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// Populated from PINNED_LANG_CHOICES + ziman.json before build() runs.
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 (_, 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(tpl.name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (_, 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(tpl.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [[code, genderLabel(code)]];
});
populateMenus(select, choices);
if ($.grep(choices, function (c) { return c[0] === prevVal; }).length) {
select.val(prevVal);
}
if (!isMobile) select.trigger("chosen:updated");
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
choices: [
["gh", "gerguhêz"],
['x-gh', 'xwerû-gerguhêz'],
['p-gh', 'pêkhatî-gerguhêz'],
['h-gh', 'hevedudanî-gerguhêz'],
['b-gh', 'biwêjî-gerguhêz'],
["ngh", "negerguhêz"],
['x-ngh', 'xwerû-negerguhêz'],
['p-ngh', 'pêkhatî-negerguhêz'],
['h-ngh', 'hevedudanî-negerguhêz'],
['b-ngh', 'biwêjî-negerguhêz'] ],
output: function () { return ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (_, tpl) {
var opt = $("<option></option>")
.val(tpl.name)
.text(tpl.name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
if (!isMobile) tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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");
srcSel.trigger("chosen:updated");
}
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");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (_, f) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(f.label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", f.key)
.attr("placeholder", f.label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Apply Chosen to the new block's template selector on desktop.
if (!isMobile) newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: [],
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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 array.
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 (_, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(tpl.name + " — " + tpl.desc));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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", "Nimûne: 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (_, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(tpl.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("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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" }));
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);
}
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);
});
});
},
/** 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);
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 "";
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) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || [];
var bLang = langsByCode[b[0]] || [];
return (aLang[2] || "").localeCompare(bLang[2] || "", "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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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",
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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
// 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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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[2] || "").localeCompare(bLang[2] || "", "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 ên (ne jêgirtinan) 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));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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 jêrê 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
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.");
});
}
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "\n* {{" + name + "}}");
$(this).val("");
if (!isMobile) sel.trigger("chosen:updated");
ta.focus();
});
wrap.append(sel).append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify chosen widgets of the programmatic value change.
if (!isMobile) fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>"));
$.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[1]);
if (choice[2] !== undefined) {
// This is a language choice [code, name, sortKey] — show code.
displayText = capitalize(choice[1]) + " (" + choice[0] + ")";
}
var option = $(mw.html.element("option", {
value: choice[0] }, displayText));
if (disabledValues[choice[0]]) 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);
if (isMobile) {
populateMenus(input, step.keys, getDisabledKeys(step));
// On mobile return the wrapper; the select is inside it.
// Change events still fire on the inner select.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
}
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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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[1] : 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
if (isMobile) {
stepDiv.append(wrapNativeSelect(input));
} else {
stepDiv.append(input);
}
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
if (!isMobile) stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
if (!isMobile) $("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
framed: false
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
if (!isMobile) startForm.find("select").chosen(chosenOptions);
refresh();
// The language list (pinned + full ziman.json list, merged) is already
// finalized in steps.lang.choices/steps.trans.keys/etc. before build()
// runs, so every select above was already created with its full option
// list — no repopulation needed here.
// Silently restore any draft the editor previously saved for this page
// (if the "Li cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// jquery.chosen/oojs-ui-core load in parallel with mediawikiApiReadyPromise
// above (mediawiki.api + the ziman.json request) — nothing here depends on
// them finishing first.
var uiModulesPromise = mw.loader.using(["jquery.chosen", "oojs-ui-core"]);
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike,
// pê re bi PINNED_LANG_CHOICES re tevlihev bike. Kodên ku bi "-pro" diqedin
// û navên ku "zimanê destan" tê de heye tên derxistin. Zimanên xardkirî li
// serî dimînin, yên din bi alfabeyê li dû wan tên.
// Merged into steps.*.choices/keys before build() ever runs, so every
// select is created with its full, final option list — no separate
// repopulation step once the form is already on screen. This doesn't
// touch data.json at all, so it's ready as soon as ziman.json is.
var langsReadyPromise = mediawikiApiReadyPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var pinnedChoices = [];
$.each(PINNED_LANG_CHOICES, function (code, name) {
pinnedChoices.push([code, name]);
});
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice[0]; });
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = [code, name, sortKey];
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey (index 2).
if (choice.length < 3) choice[2] = choice[1];
langsByCode[choice[0]] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a[2].localeCompare(b[2], userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
});
// Fetches data.json (metadata, gender labels, POS choices, template defs)
// and applies it. Called only once the form is already built, since none
// of this is needed for the initial render — the pos/pron/etym/dfn steps
// that depend on it are only built lazily, once the editor reaches them
// (see refresh()'s step.visible() gate), by which point this will
// ordinarily have already resolved.
function loadDataJson() {
new mw.Api().get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
}).then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
if (d.metadata) $.extend(metadata, d.metadata);
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
if (d.posChoices) steps.pos.choices = d.posChoices;
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
if (d.posExamples) steps.dfn.posExamples = d.posExamples;
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
});
}
// Only build (and show) the form once the UI modules are loaded AND the
// full, merged language list is ready — so the buttons and language
// dropdown appear complete on first paint instead of popping in a moment
// after the rest of the form. data.json loads afterward, in the background.
$.when(uiModulesPromise, langsReadyPromise).always(function () {
build();
loadDataJson();
});
});
//</nowiki>
r9fngi12l6cukq7jpctixfmmp8vhgkv
6291581
6291580
2026-07-26T17:29:22Z
Wikihez
7344
Restored revision 6291577 by [[Special:Contributions/Wikihez|Wikihez]] ([[User talk:Wikihez|talk]]) ([[:m:User:Xiplus/TwinkleGlobal|TwinkleGlobal]])
6291581
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
function wrapNativeSelect(select) {
var placeholder = select.attr("data-placeholder") || "Lê bigere…";
// Hide the native select — we drive it programmatically.
select.hide();
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results'></ul>").hide();
// Full option cache — rebuilt when chosen:updated fires.
var allOptions = [];
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
allOptions.push({
value: v,
text: $(this).text(),
disabled: $(this).prop("disabled")
});
});
}
cacheOptions();
function showResults(query) {
var q = query.trim().toLowerCase();
resultsList.empty();
var matched;
if (q) {
matched = [];
$.each(allOptions, function (_, o) {
if (o.disabled) return;
var codeMatch = o.value.toLowerCase().indexOf(q) !== -1;
var nameMatch = o.text.toLowerCase().indexOf(q) !== -1;
if (codeMatch || nameMatch) {
matched.push({ opt: o, codeMatch: codeMatch });
}
});
// Code matches (especially prefix matches) come first.
matched.sort(function (a, b) {
var aCode = a.opt.value.toLowerCase();
var bCode = b.opt.value.toLowerCase();
var aPrefix = aCode.indexOf(q) === 0;
var bPrefix = bCode.indexOf(q) === 0;
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
if (a.codeMatch !== b.codeMatch) return a.codeMatch ? -1 : 1;
return 0;
});
matched = matched.map(function (item) { return item.opt; });
} else {
matched = allOptions.filter(function (o) { return !o.disabled; });
}
if (!matched.length) {
resultsList.hide();
return;
}
$.each(matched.slice(0, 50), function (_, opt) {
var li = $("<li></li>").text(opt.text).attr("data-value", opt.value);
// 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);
});
// Keep mousedown for desktop fallback.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
resultsList.append(li);
});
// Position the list above or below depending on available 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%"
});
resultsList.show();
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
resultsList.hide();
select.trigger("change");
}
textInput.on("input focus", function () {
showResults($(this).val());
});
textInput.on("blur", function () {
// Short delay so a touchstart on a result item fires first.
setTimeout(function () { resultsList.hide(); }, 200);
});
// chosen:updated → re-cache (called after ziman.json loads).
select.on("chosen:updated", function () {
cacheOptions();
// If a value is already selected, keep the display text in sync.
var cur = select.val();
if (cur) {
var match = $.grep(allOptions, function (o) { return o.value === cur; })[0];
if (match) textInput.val(match.text);
}
});
// No-op .chosen() so desktop call-sites don't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c[0]] = 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// 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 (_, 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(tpl.name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (_, 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(tpl.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [[code, genderLabel(code)]];
});
populateMenus(select, choices);
if ($.grep(choices, function (c) { return c[0] === prevVal; }).length) {
select.val(prevVal);
}
if (!isMobile) select.trigger("chosen:updated");
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
choices: [
["gh", "gerguhêz"],
['x-gh', 'xwerû-gerguhêz'],
['p-gh', 'pêkhatî-gerguhêz'],
['h-gh', 'hevedudanî-gerguhêz'],
['b-gh', 'biwêjî-gerguhêz'],
["ngh", "negerguhêz"],
['x-ngh', 'xwerû-negerguhêz'],
['p-ngh', 'pêkhatî-negerguhêz'],
['h-ngh', 'hevedudanî-negerguhêz'],
['b-ngh', 'biwêjî-negerguhêz'] ],
output: function () { return ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (_, tpl) {
var opt = $("<option></option>")
.val(tpl.name)
.text(tpl.name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
if (!isMobile) tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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");
srcSel.trigger("chosen:updated");
}
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");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (_, f) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(f.label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", f.key)
.attr("placeholder", f.label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Apply Chosen to the new block's template selector on desktop.
if (!isMobile) newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: [],
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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 array.
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 (_, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(tpl.name + " — " + tpl.desc));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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", "Nimûne: 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (_, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(tpl.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("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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" }));
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);
}
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);
});
});
},
/** 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);
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 "";
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) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || [];
var bLang = langsByCode[b[0]] || [];
return (aLang[2] || "").localeCompare(bLang[2] || "", "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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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",
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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
// 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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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[2] || "").localeCompare(bLang[2] || "", "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 ên (ne jêgirtinan) 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));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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 jêrê 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
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.");
});
}
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "\n* {{" + name + "}}");
$(this).val("");
if (!isMobile) sel.trigger("chosen:updated");
ta.focus();
});
wrap.append(sel).append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify chosen widgets of the programmatic value change.
if (!isMobile) fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>"));
$.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[1]);
if (choice[2] !== undefined) {
// This is a language choice [code, name, sortKey] — show code.
displayText = capitalize(choice[1]) + " (" + choice[0] + ")";
}
var option = $(mw.html.element("option", {
value: choice[0] }, displayText));
if (disabledValues[choice[0]]) 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);
if (isMobile) {
populateMenus(input, step.keys, getDisabledKeys(step));
// On mobile return the wrapper; the select is inside it.
// Change events still fire on the inner select.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
}
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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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[1] : 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
if (isMobile) {
stepDiv.append(wrapNativeSelect(input));
} else {
stepDiv.append(input);
}
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
if (!isMobile) stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
if (!isMobile) $("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
framed: false
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
if (!isMobile) startForm.find("select").chosen(chosenOptions);
refresh();
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Pinned choices come from MediaWiki:Gadget-start.js/data.json (langChoices),
// already loaded into steps.lang.choices before build() runs.
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice[0]; });
zimanJsonPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = [code, name, sortKey];
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey (index 2).
if (choice.length < 3) choice[2] = choice[1];
langsByCode[choice[0]] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a[2].localeCompare(b[2], userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
var langMenu = $("#wiktku-start-lang-field");
populateMenus(langMenu, steps.lang.choices);
langMenu.trigger("chosen:updated");
var transMenus = $("#wiktku-start-trans .wiktku-start-step-keyfield");
populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
transMenus.trigger("chosen:updated");
var dundeMenus = $("#wiktku-start-dunde .wiktku-start-step-keyfield");
populateMenus(dundeMenus, steps.dunde.keys, getDisabledKeys(steps.dunde));
dundeMenus.trigger("chosen:updated");
var zaravaMenus = $("#wiktku-start-zarava .wiktku-start-step-keyfield");
populateMenus(zaravaMenus, steps.zarava.keys, getDisabledKeys(steps.zarava));
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page (if the "Li
// cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
});
}
// jquery.chosen/oojs-ui-core load in parallel with metaApiPromise above
// (mediawiki.api + the two API requests) — this block only needs to wait
// on the UI modules itself.
mw.loader.using(["jquery.chosen", "oojs-ui-core"], function () {
if (!isMobile) {
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
}
// Load metadata, langChoices, posChoices and tplDefs from the companion
// data page so they can be updated without touching this script.
// (Request was already fired via metaApiPromise above, in parallel
// with this module load — we're just picking up the result here.)
metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Populate lang choices (pinned list used by build() → ziman.json loader).
if (d.langChoices) steps.lang.choices = d.langChoices;
// Populate POS choices.
if (d.posChoices) steps.pos.choices = d.posChoices;
// 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.dfn.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 (now populated) lang choices.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
}).always(build);
});
});
//</nowiki>
2dpgy94jef0yyeungjul1j7njo4ndty
6291582
6291581
2026-07-26T17:30:27Z
Wikihez
7344
6291582
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
function wrapNativeSelect(select) {
var placeholder = select.attr("data-placeholder") || "Lê bigere…";
// Hide the native select — we drive it programmatically.
select.hide();
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results'></ul>").hide();
// Full option cache — rebuilt when chosen:updated fires.
var allOptions = [];
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
allOptions.push({
value: v,
text: $(this).text(),
disabled: $(this).prop("disabled")
});
});
}
cacheOptions();
function showResults(query) {
var q = query.trim().toLowerCase();
resultsList.empty();
var matched;
if (q) {
matched = [];
$.each(allOptions, function (_, o) {
if (o.disabled) return;
var codeMatch = o.value.toLowerCase().indexOf(q) !== -1;
var nameMatch = o.text.toLowerCase().indexOf(q) !== -1;
if (codeMatch || nameMatch) {
matched.push({ opt: o, codeMatch: codeMatch });
}
});
// Code matches (especially prefix matches) come first.
matched.sort(function (a, b) {
var aCode = a.opt.value.toLowerCase();
var bCode = b.opt.value.toLowerCase();
var aPrefix = aCode.indexOf(q) === 0;
var bPrefix = bCode.indexOf(q) === 0;
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
if (a.codeMatch !== b.codeMatch) return a.codeMatch ? -1 : 1;
return 0;
});
matched = matched.map(function (item) { return item.opt; });
} else {
matched = allOptions.filter(function (o) { return !o.disabled; });
}
if (!matched.length) {
resultsList.hide();
return;
}
$.each(matched.slice(0, 50), function (_, opt) {
var li = $("<li></li>").text(opt.text).attr("data-value", opt.value);
// 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);
});
// Keep mousedown for desktop fallback.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
resultsList.append(li);
});
// Position the list above or below depending on available 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%"
});
resultsList.show();
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
resultsList.hide();
select.trigger("change");
}
textInput.on("input focus", function () {
showResults($(this).val());
});
textInput.on("blur", function () {
// Short delay so a touchstart on a result item fires first.
setTimeout(function () { resultsList.hide(); }, 200);
});
// chosen:updated → re-cache (called after ziman.json loads).
select.on("chosen:updated", function () {
cacheOptions();
// If a value is already selected, keep the display text in sync.
var cur = select.val();
if (cur) {
var match = $.grep(allOptions, function (o) { return o.value === cur; })[0];
if (match) textInput.val(match.text);
}
});
// No-op .chosen() so desktop call-sites don't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c[0]] = 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// 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 (_, 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(tpl.name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (_, 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(tpl.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [[code, genderLabel(code)]];
});
populateMenus(select, choices);
if ($.grep(choices, function (c) { return c[0] === prevVal; }).length) {
select.val(prevVal);
}
if (!isMobile) select.trigger("chosen:updated");
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
choices: [
["gh", "gerguhêz"],
['x-gh', 'xwerû-gerguhêz'],
['p-gh', 'pêkhatî-gerguhêz'],
['h-gh', 'hevedudanî-gerguhêz'],
['b-gh', 'biwêjî-gerguhêz'],
["ngh", "negerguhêz"],
['x-ngh', 'xwerû-negerguhêz'],
['p-ngh', 'pêkhatî-negerguhêz'],
['h-ngh', 'hevedudanî-negerguhêz'],
['b-ngh', 'biwêjî-negerguhêz'] ],
output: function () { return ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (_, tpl) {
var opt = $("<option></option>")
.val(tpl.name)
.text(tpl.name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
if (!isMobile) tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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");
srcSel.trigger("chosen:updated");
}
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");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (_, f) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(f.label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", f.key)
.attr("placeholder", f.label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Apply Chosen to the new block's template selector on desktop.
if (!isMobile) newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: [],
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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 array.
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 (_, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(tpl.name + " — " + tpl.desc));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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", "Nimûne: 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (_, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(tpl.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("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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" }));
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);
}
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);
});
});
},
/** 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);
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 "";
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) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || [];
var bLang = langsByCode[b[0]] || [];
return (aLang[2] || "").localeCompare(bLang[2] || "", "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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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",
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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
// 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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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[2] || "").localeCompare(bLang[2] || "", "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 ên (ne jêgirtinan) 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));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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 jêrê 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
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.");
});
}
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "\n* {{" + name + "}}");
$(this).val("");
if (!isMobile) sel.trigger("chosen:updated");
ta.focus();
});
wrap.append(sel).append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify chosen widgets of the programmatic value change.
if (!isMobile) fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>"));
$.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[1]);
if (choice[2] !== undefined) {
// This is a language choice [code, name, sortKey] — show code.
displayText = capitalize(choice[1]) + " (" + choice[0] + ")";
}
var option = $(mw.html.element("option", {
value: choice[0] }, displayText));
if (disabledValues[choice[0]]) 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);
if (isMobile) {
populateMenus(input, step.keys, getDisabledKeys(step));
// On mobile return the wrapper; the select is inside it.
// Change events still fire on the inner select.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
}
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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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[1] : 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
if (isMobile) {
stepDiv.append(wrapNativeSelect(input));
} else {
stepDiv.append(input);
}
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
if (!isMobile) stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
if (!isMobile) $("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
framed: false
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
if (!isMobile) startForm.find("select").chosen(chosenOptions);
refresh();
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Pinned choices come from MediaWiki:Gadget-start.js/data.json (langChoices),
// already loaded into steps.lang.choices before build() runs.
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice[0]; });
zimanJsonPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = [code, name, sortKey];
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey (index 2).
if (choice.length < 3) choice[2] = choice[1];
langsByCode[choice[0]] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a[2].localeCompare(b[2], userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
var langMenu = $("#wiktku-start-lang-field");
populateMenus(langMenu, steps.lang.choices);
langMenu.trigger("chosen:updated");
var transMenus = $("#wiktku-start-trans .wiktku-start-step-keyfield");
populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
transMenus.trigger("chosen:updated");
var dundeMenus = $("#wiktku-start-dunde .wiktku-start-step-keyfield");
populateMenus(dundeMenus, steps.dunde.keys, getDisabledKeys(steps.dunde));
dundeMenus.trigger("chosen:updated");
var zaravaMenus = $("#wiktku-start-zarava .wiktku-start-step-keyfield");
populateMenus(zaravaMenus, steps.zarava.keys, getDisabledKeys(steps.zarava));
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page (if the "Li
// cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
});
}
// jquery.chosen/oojs-ui-core load in parallel with metaApiPromise above
// (mediawiki.api + the two API requests) — this block only needs to wait
// on the UI modules itself.
mw.loader.using(["jquery.chosen", "oojs-ui-core"], function () {
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
// Load metadata, langChoices, posChoices and tplDefs from the companion
// data page so they can be updated without touching this script.
// (Request was already fired via metaApiPromise above, in parallel
// with this module load — we're just picking up the result here.)
metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Populate lang choices (pinned list used by build() → ziman.json loader).
if (d.langChoices) steps.lang.choices = d.langChoices;
// Populate POS choices.
if (d.posChoices) steps.pos.choices = d.posChoices;
// 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.dfn.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 (now populated) lang choices.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
}).always(build);
});
});
//</nowiki>
q75ymz0jfogy00s6x5vit4wjf9kb0en
6291587
6291582
2026-07-26T18:05:33Z
Wikihez
7344
6291587
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
function wrapNativeSelect(select) {
var placeholder = select.attr("data-placeholder") || "Lê bigere…";
// Hide the native select — we drive it programmatically.
select.hide();
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results'></ul>").hide();
// Full option cache — rebuilt when chosen:updated fires.
var allOptions = [];
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
allOptions.push({
value: v,
text: $(this).text(),
disabled: $(this).prop("disabled")
});
});
}
cacheOptions();
function showResults(query) {
var q = query.trim().toLowerCase();
resultsList.empty();
var matched;
if (q) {
matched = [];
$.each(allOptions, function (_, o) {
if (o.disabled) return;
var codeMatch = o.value.toLowerCase().indexOf(q) !== -1;
var nameMatch = o.text.toLowerCase().indexOf(q) !== -1;
if (codeMatch || nameMatch) {
matched.push({ opt: o, codeMatch: codeMatch });
}
});
// Code matches (especially prefix matches) come first.
matched.sort(function (a, b) {
var aCode = a.opt.value.toLowerCase();
var bCode = b.opt.value.toLowerCase();
var aPrefix = aCode.indexOf(q) === 0;
var bPrefix = bCode.indexOf(q) === 0;
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
if (a.codeMatch !== b.codeMatch) return a.codeMatch ? -1 : 1;
return 0;
});
matched = matched.map(function (item) { return item.opt; });
} else {
matched = allOptions.filter(function (o) { return !o.disabled; });
}
if (!matched.length) {
resultsList.hide();
return;
}
$.each(matched.slice(0, 50), function (_, opt) {
var li = $("<li></li>").text(opt.text).attr("data-value", opt.value);
// 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);
});
// Keep mousedown for desktop fallback.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
resultsList.append(li);
});
// Position the list above or below depending on available 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%"
});
resultsList.show();
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
resultsList.hide();
select.trigger("change");
}
textInput.on("input focus", function () {
showResults($(this).val());
});
textInput.on("blur", function () {
// Short delay so a touchstart on a result item fires first.
setTimeout(function () { resultsList.hide(); }, 200);
});
// chosen:updated → re-cache (called after ziman.json loads).
select.on("chosen:updated", function () {
cacheOptions();
// If a value is already selected, keep the display text in sync.
var cur = select.val();
if (cur) {
var match = $.grep(allOptions, function (o) { return o.value === cur; })[0];
if (match) textInput.val(match.text);
}
});
// No-op .chosen() so desktop call-sites don't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a plain {code: label} object (the format used for langChoices
// and posChoices in data.json) into the ordered array of choice objects
// ({code, label[, sortKey]}) that the rest of this script works with.
// isLang: true adds a sortKey (defaults to label) and tells populateMenus
// to show "(code)" after the label — used for 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// 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" : ""));
}
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [{ code: code, label: genderLabel(code) }];
});
populateMenus(select, choices);
if ($.grep(choices, function (c) { return c.code === prevVal; }).length) {
select.val(prevVal);
}
if (!isMobile) select.trigger("chosen:updated");
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
choices: [
["gh", "gerguhêz"],
['x-gh', 'xwerû-gerguhêz'],
['p-gh', 'pêkhatî-gerguhêz'],
['h-gh', 'hevedudanî-gerguhêz'],
['b-gh', 'biwêjî-gerguhêz'],
["ngh", "negerguhêz"],
['x-ngh', 'xwerû-negerguhêz'],
['p-ngh', 'pêkhatî-negerguhêz'],
['h-ngh', 'hevedudanî-negerguhêz'],
['b-ngh', 'biwêjî-negerguhêz'] ],
output: function () { return ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
var opt = $("<option></option>")
.val(name)
.text(name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
if (!isMobile) tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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");
srcSel.trigger("chosen:updated");
}
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");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (key, label) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", key)
.attr("placeholder", label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Apply Chosen to the new block's template selector on desktop.
if (!isMobile) newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: {},
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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", "Nimûne: 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.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("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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" }));
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);
}
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);
});
});
},
/** 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);
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 "";
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) 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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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",
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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
// 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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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 ên (ne jêgirtinan) 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));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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 jêrê 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
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.");
});
}
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "\n* {{" + name + "}}");
$(this).val("");
if (!isMobile) sel.trigger("chosen:updated");
ta.focus();
});
wrap.append(sel).append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify chosen widgets of the programmatic value change.
if (!isMobile) fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>"));
$.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);
if (isMobile) {
populateMenus(input, step.keys, getDisabledKeys(step));
// On mobile return the wrapper; the select is inside it.
// Change events still fire on the inner select.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
}
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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
if (isMobile) {
stepDiv.append(wrapNativeSelect(input));
} else {
stepDiv.append(input);
}
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
if (!isMobile) stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
if (!isMobile) $("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
framed: false
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
if (!isMobile) startForm.find("select").chosen(chosenOptions);
refresh();
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Pinned choices come from MediaWiki:Gadget-start.js/data.json (langChoices),
// already loaded into steps.lang.choices before build() runs.
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice.code; });
zimanJsonPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = { code: code, label: name, sortKey: sortKey };
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey.
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
var langMenu = $("#wiktku-start-lang-field");
populateMenus(langMenu, steps.lang.choices);
langMenu.trigger("chosen:updated");
var transMenus = $("#wiktku-start-trans .wiktku-start-step-keyfield");
populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
transMenus.trigger("chosen:updated");
var dundeMenus = $("#wiktku-start-dunde .wiktku-start-step-keyfield");
populateMenus(dundeMenus, steps.dunde.keys, getDisabledKeys(steps.dunde));
dundeMenus.trigger("chosen:updated");
var zaravaMenus = $("#wiktku-start-zarava .wiktku-start-step-keyfield");
populateMenus(zaravaMenus, steps.zarava.keys, getDisabledKeys(steps.zarava));
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page (if the "Li
// cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
});
}
// jquery.chosen/oojs-ui-core load in parallel with metaApiPromise above
// (mediawiki.api + the two API requests) — this block only needs to wait
// on the UI modules itself.
mw.loader.using(["jquery.chosen", "oojs-ui-core"], function () {
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
// Load metadata, langChoices, posChoices and tplDefs from the companion
// data page so they can be updated without touching this script.
// (Request was already fired via metaApiPromise above, in parallel
// with this module load — we're just picking up the result here.)
metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Populate lang choices (pinned list used by build() → ziman.json loader).
// data.json stores these as a plain {code: label} object.
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// 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.dfn.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 (now populated) lang choices.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
}).always(build);
});
});
//</nowiki>
ij43mnwfsxa2sfqfodaix0omguumzlc
6291588
6291587
2026-07-26T18:07:50Z
Wikihez
7344
6291588
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
function wrapNativeSelect(select) {
var placeholder = select.attr("data-placeholder") || "Lê bigere…";
// Hide the native select — we drive it programmatically.
select.hide();
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results'></ul>").hide();
// Full option cache — rebuilt when chosen:updated fires.
var allOptions = [];
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
allOptions.push({
value: v,
text: $(this).text(),
disabled: $(this).prop("disabled")
});
});
}
cacheOptions();
function showResults(query) {
var q = query.trim().toLowerCase();
resultsList.empty();
var matched;
if (q) {
matched = [];
$.each(allOptions, function (_, o) {
if (o.disabled) return;
var codeMatch = o.value.toLowerCase().indexOf(q) !== -1;
var nameMatch = o.text.toLowerCase().indexOf(q) !== -1;
if (codeMatch || nameMatch) {
matched.push({ opt: o, codeMatch: codeMatch });
}
});
// Code matches (especially prefix matches) come first.
matched.sort(function (a, b) {
var aCode = a.opt.value.toLowerCase();
var bCode = b.opt.value.toLowerCase();
var aPrefix = aCode.indexOf(q) === 0;
var bPrefix = bCode.indexOf(q) === 0;
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
if (a.codeMatch !== b.codeMatch) return a.codeMatch ? -1 : 1;
return 0;
});
matched = matched.map(function (item) { return item.opt; });
} else {
matched = allOptions.filter(function (o) { return !o.disabled; });
}
if (!matched.length) {
resultsList.hide();
return;
}
$.each(matched.slice(0, 50), function (_, opt) {
var li = $("<li></li>").text(opt.text).attr("data-value", opt.value);
// 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);
});
// Keep mousedown for desktop fallback.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
resultsList.append(li);
});
// Position the list above or below depending on available 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%"
});
resultsList.show();
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
resultsList.hide();
select.trigger("change");
}
textInput.on("input focus", function () {
showResults($(this).val());
});
textInput.on("blur", function () {
// Short delay so a touchstart on a result item fires first.
setTimeout(function () { resultsList.hide(); }, 200);
});
// chosen:updated → re-cache (called after ziman.json loads).
select.on("chosen:updated", function () {
cacheOptions();
// If a value is already selected, keep the display text in sync.
var cur = select.val();
if (cur) {
var match = $.grep(allOptions, function (o) { return o.value === cur; })[0];
if (match) textInput.val(match.text);
}
});
// No-op .chosen() so desktop call-sites don't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a plain {code: label} object (the format used for langChoices
// and posChoices in data.json) into the ordered array of choice objects
// ({code, label[, sortKey]}) that the rest of this script works with.
// isLang: true adds a sortKey (defaults to label) and tells populateMenus
// to show "(code)" after the label — used for 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// 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" : ""));
}
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [{ code: code, label: genderLabel(code) }];
});
populateMenus(select, choices);
if ($.grep(choices, function (c) { return c.code === prevVal; }).length) {
select.val(prevVal);
}
if (!isMobile) select.trigger("chosen:updated");
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
choices: [
["gh", "gerguhêz"],
['x-gh', 'xwerû-gerguhêz'],
['p-gh', 'pêkhatî-gerguhêz'],
['h-gh', 'hevedudanî-gerguhêz'],
['b-gh', 'biwêjî-gerguhêz'],
["ngh", "negerguhêz"],
['x-ngh', 'xwerû-negerguhêz'],
['p-ngh', 'pêkhatî-negerguhêz'],
['h-ngh', 'hevedudanî-negerguhêz'],
['b-ngh', 'biwêjî-negerguhêz'] ],
output: function () { return ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
var opt = $("<option></option>")
.val(name)
.text(name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
if (!isMobile) tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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");
srcSel.trigger("chosen:updated");
}
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");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (key, label) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", key)
.attr("placeholder", label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Apply Chosen to the new block's template selector on desktop.
if (!isMobile) newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: {},
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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", "Nimûne: 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.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("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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" }));
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);
}
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);
});
});
},
/** 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);
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 "";
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) 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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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",
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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
// 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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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 ên (ne jêgirtinan) 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));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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 jêrê 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
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.");
});
}
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "\n* {{" + name + "}}");
$(this).val("");
if (!isMobile) sel.trigger("chosen:updated");
ta.focus();
});
wrap.append(sel).append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify chosen widgets of the programmatic value change.
if (!isMobile) fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>"));
$.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);
if (isMobile) {
populateMenus(input, step.keys, getDisabledKeys(step));
// On mobile return the wrapper; the select is inside it.
// Change events still fire on the inner select.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
}
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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
if (isMobile) {
stepDiv.append(wrapNativeSelect(input));
} else {
stepDiv.append(input);
}
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
if (!isMobile) stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
if (!isMobile) $("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
framed: false
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
if (!isMobile) startForm.find("select").chosen(chosenOptions);
refresh();
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Pinned choices come from MediaWiki:Gadget-start.js/data.json (langChoices),
// already loaded into steps.lang.choices before build() runs.
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice.code; });
zimanJsonPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = { code: code, label: name, sortKey: sortKey };
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey.
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
var langMenu = $("#wiktku-start-lang-field");
populateMenus(langMenu, steps.lang.choices);
langMenu.trigger("chosen:updated");
var transMenus = $("#wiktku-start-trans .wiktku-start-step-keyfield");
populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
transMenus.trigger("chosen:updated");
var dundeMenus = $("#wiktku-start-dunde .wiktku-start-step-keyfield");
populateMenus(dundeMenus, steps.dunde.keys, getDisabledKeys(steps.dunde));
dundeMenus.trigger("chosen:updated");
var zaravaMenus = $("#wiktku-start-zarava .wiktku-start-step-keyfield");
populateMenus(zaravaMenus, steps.zarava.keys, getDisabledKeys(steps.zarava));
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page (if the "Li
// cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
});
}
// jquery.chosen/oojs-ui-core load in parallel with metaApiPromise above
// (mediawiki.api + the two API requests) — this block only needs to wait
// on the UI modules itself.
mw.loader.using(["jquery.chosen", "oojs-ui-core"], function () {
// Load metadata, langChoices, posChoices and tplDefs from the companion
// data page so they can be updated without touching this script.
// (Request was already fired via metaApiPromise above, in parallel
// with this module load — we're just picking up the result here.)
metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Populate lang choices (pinned list used by build() → ziman.json loader).
// data.json stores these as a plain {code: label} object.
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// 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.dfn.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 (now populated) lang choices.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
}).always(build);
});
});
//</nowiki>
8unyik6xvvsghe5h9oumevrqsr53269
6291590
6291588
2026-07-26T18:41:25Z
Wikihez
7344
6291590
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
function wrapNativeSelect(select) {
var placeholder = select.attr("data-placeholder") || "Lê bigere…";
// Hide the native select — we drive it programmatically.
select.hide();
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results'></ul>").hide();
// Full option cache — rebuilt when chosen:updated fires.
var allOptions = [];
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
allOptions.push({
value: v,
text: $(this).text(),
disabled: $(this).prop("disabled")
});
});
}
cacheOptions();
function showResults(query) {
var q = query.trim().toLowerCase();
resultsList.empty();
var matched;
if (q) {
matched = [];
$.each(allOptions, function (_, o) {
if (o.disabled) return;
var codeMatch = o.value.toLowerCase().indexOf(q) !== -1;
var nameMatch = o.text.toLowerCase().indexOf(q) !== -1;
if (codeMatch || nameMatch) {
matched.push({ opt: o, codeMatch: codeMatch });
}
});
// Code matches (especially prefix matches) come first.
matched.sort(function (a, b) {
var aCode = a.opt.value.toLowerCase();
var bCode = b.opt.value.toLowerCase();
var aPrefix = aCode.indexOf(q) === 0;
var bPrefix = bCode.indexOf(q) === 0;
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
if (a.codeMatch !== b.codeMatch) return a.codeMatch ? -1 : 1;
return 0;
});
matched = matched.map(function (item) { return item.opt; });
} else {
matched = allOptions.filter(function (o) { return !o.disabled; });
}
if (!matched.length) {
resultsList.hide();
return;
}
$.each(matched.slice(0, 50), function (_, opt) {
var li = $("<li></li>").text(opt.text).attr("data-value", opt.value);
// 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);
});
// Keep mousedown for desktop fallback.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
resultsList.append(li);
});
// Position the list above or below depending on available 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%"
});
resultsList.show();
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
resultsList.hide();
select.trigger("change");
}
textInput.on("input focus", function () {
showResults($(this).val());
});
textInput.on("blur", function () {
// Short delay so a touchstart on a result item fires first.
setTimeout(function () { resultsList.hide(); }, 200);
});
// chosen:updated → re-cache (called after ziman.json loads).
select.on("chosen:updated", function () {
cacheOptions();
// If a value is already selected, keep the display text in sync.
var cur = select.val();
if (cur) {
var match = $.grep(allOptions, function (o) { return o.value === cur; })[0];
if (match) textInput.val(match.text);
}
});
// No-op .chosen() so desktop call-sites don't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a plain {code: label} object (the format used for langChoices
// and posChoices in data.json) into the ordered array of choice objects
// ({code, label[, sortKey]}) that the rest of this script works with.
// isLang: true adds a sortKey (defaults to label) and tells populateMenus
// to show "(code)" after the label — used for 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// 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" : ""));
}
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [{ code: code, label: genderLabel(code) }];
});
populateMenus(select, choices);
if ($.grep(choices, function (c) { return c.code === prevVal; }).length) {
select.val(prevVal);
}
if (!isMobile) select.trigger("chosen:updated");
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
choices: [
["gh", "gerguhêz"],
['x-gh', 'xwerû-gerguhêz'],
['p-gh', 'pêkhatî-gerguhêz'],
['h-gh', 'hevedudanî-gerguhêz'],
['b-gh', 'biwêjî-gerguhêz'],
["ngh", "negerguhêz"],
['x-ngh', 'xwerû-negerguhêz'],
['p-ngh', 'pêkhatî-negerguhêz'],
['h-ngh', 'hevedudanî-negerguhêz'],
['b-ngh', 'biwêjî-negerguhêz'] ],
output: function () { return ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
var opt = $("<option></option>")
.val(name)
.text(name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
if (!isMobile) tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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");
srcSel.trigger("chosen:updated");
}
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");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (key, label) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", key)
.attr("placeholder", label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Apply Chosen to the new block's template selector on desktop.
if (!isMobile) newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: {},
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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", "Nimûne: 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.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("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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" }));
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);
}
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);
});
});
},
/** 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);
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 "";
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) 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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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",
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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
// 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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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 ên (ne jêgirtinan) 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));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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 jêrê 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
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.");
});
}
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "\n* {{" + name + "}}");
$(this).val("");
if (!isMobile) sel.trigger("chosen:updated");
ta.focus();
});
wrap.append(sel).append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify chosen widgets of the programmatic value change.
if (!isMobile) fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>"));
$.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);
if (isMobile) {
populateMenus(input, step.keys, getDisabledKeys(step));
// On mobile return the wrapper; the select is inside it.
// Change events still fire on the inner select.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
}
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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
if (isMobile) {
stepDiv.append(wrapNativeSelect(input));
} else {
stepDiv.append(input);
}
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
if (!isMobile) stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
if (!isMobile) $("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
framed: false
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
if (!isMobile) startForm.find("select").chosen(chosenOptions);
refresh();
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Pinned choices come from MediaWiki:Gadget-start.js/data.json (langChoices),
// already loaded into steps.lang.choices before build() runs.
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice.code; });
zimanJsonPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = { code: code, label: name, sortKey: sortKey };
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey.
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
var langMenu = $("#wiktku-start-lang-field");
populateMenus(langMenu, steps.lang.choices);
langMenu.trigger("chosen:updated");
var transMenus = $("#wiktku-start-trans .wiktku-start-step-keyfield");
populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
transMenus.trigger("chosen:updated");
var dundeMenus = $("#wiktku-start-dunde .wiktku-start-step-keyfield");
populateMenus(dundeMenus, steps.dunde.keys, getDisabledKeys(steps.dunde));
dundeMenus.trigger("chosen:updated");
var zaravaMenus = $("#wiktku-start-zarava .wiktku-start-step-keyfield");
populateMenus(zaravaMenus, steps.zarava.keys, getDisabledKeys(steps.zarava));
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page (if the "Li
// cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
});
}
// jquery.chosen/oojs-ui-core load in parallel with metaApiPromise above
// (mediawiki.api + the two API requests) — this block only needs to wait
// on the UI modules itself.
mw.loader.using(["jquery.chosen", "oojs-ui-core"], function () {
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
// Load metadata, langChoices, posChoices and tplDefs from the companion
// data page so they can be updated without touching this script.
// (Request was already fired via metaApiPromise above, in parallel
// with this module load — we're just picking up the result here.)
metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Populate lang choices (pinned list used by build() → ziman.json loader).
// data.json stores these as a plain {code: label} object.
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// 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.dfn.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 (now populated) lang choices.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
}).always(build);
});
});
//</nowiki>
ij43mnwfsxa2sfqfodaix0omguumzlc
6291591
6291590
2026-07-26T18:41:37Z
Wikihez
7344
Restored revision 6291588 by [[Special:Contributions/Wikihez|Wikihez]] ([[User talk:Wikihez|talk]]) ([[:m:User:Xiplus/TwinkleGlobal|TwinkleGlobal]])
6291591
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
function wrapNativeSelect(select) {
var placeholder = select.attr("data-placeholder") || "Lê bigere…";
// Hide the native select — we drive it programmatically.
select.hide();
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results'></ul>").hide();
// Full option cache — rebuilt when chosen:updated fires.
var allOptions = [];
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
allOptions.push({
value: v,
text: $(this).text(),
disabled: $(this).prop("disabled")
});
});
}
cacheOptions();
function showResults(query) {
var q = query.trim().toLowerCase();
resultsList.empty();
var matched;
if (q) {
matched = [];
$.each(allOptions, function (_, o) {
if (o.disabled) return;
var codeMatch = o.value.toLowerCase().indexOf(q) !== -1;
var nameMatch = o.text.toLowerCase().indexOf(q) !== -1;
if (codeMatch || nameMatch) {
matched.push({ opt: o, codeMatch: codeMatch });
}
});
// Code matches (especially prefix matches) come first.
matched.sort(function (a, b) {
var aCode = a.opt.value.toLowerCase();
var bCode = b.opt.value.toLowerCase();
var aPrefix = aCode.indexOf(q) === 0;
var bPrefix = bCode.indexOf(q) === 0;
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
if (a.codeMatch !== b.codeMatch) return a.codeMatch ? -1 : 1;
return 0;
});
matched = matched.map(function (item) { return item.opt; });
} else {
matched = allOptions.filter(function (o) { return !o.disabled; });
}
if (!matched.length) {
resultsList.hide();
return;
}
$.each(matched.slice(0, 50), function (_, opt) {
var li = $("<li></li>").text(opt.text).attr("data-value", opt.value);
// 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);
});
// Keep mousedown for desktop fallback.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
resultsList.append(li);
});
// Position the list above or below depending on available 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%"
});
resultsList.show();
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
resultsList.hide();
select.trigger("change");
}
textInput.on("input focus", function () {
showResults($(this).val());
});
textInput.on("blur", function () {
// Short delay so a touchstart on a result item fires first.
setTimeout(function () { resultsList.hide(); }, 200);
});
// chosen:updated → re-cache (called after ziman.json loads).
select.on("chosen:updated", function () {
cacheOptions();
// If a value is already selected, keep the display text in sync.
var cur = select.val();
if (cur) {
var match = $.grep(allOptions, function (o) { return o.value === cur; })[0];
if (match) textInput.val(match.text);
}
});
// No-op .chosen() so desktop call-sites don't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a plain {code: label} object (the format used for langChoices
// and posChoices in data.json) into the ordered array of choice objects
// ({code, label[, sortKey]}) that the rest of this script works with.
// isLang: true adds a sortKey (defaults to label) and tells populateMenus
// to show "(code)" after the label — used for 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// 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" : ""));
}
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [{ code: code, label: genderLabel(code) }];
});
populateMenus(select, choices);
if ($.grep(choices, function (c) { return c.code === prevVal; }).length) {
select.val(prevVal);
}
if (!isMobile) select.trigger("chosen:updated");
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
choices: [
["gh", "gerguhêz"],
['x-gh', 'xwerû-gerguhêz'],
['p-gh', 'pêkhatî-gerguhêz'],
['h-gh', 'hevedudanî-gerguhêz'],
['b-gh', 'biwêjî-gerguhêz'],
["ngh", "negerguhêz"],
['x-ngh', 'xwerû-negerguhêz'],
['p-ngh', 'pêkhatî-negerguhêz'],
['h-ngh', 'hevedudanî-negerguhêz'],
['b-ngh', 'biwêjî-negerguhêz'] ],
output: function () { return ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
var opt = $("<option></option>")
.val(name)
.text(name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
if (!isMobile) tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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");
srcSel.trigger("chosen:updated");
}
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");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (key, label) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", key)
.attr("placeholder", label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Apply Chosen to the new block's template selector on desktop.
if (!isMobile) newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: {},
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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", "Nimûne: 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.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("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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" }));
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);
}
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);
});
});
},
/** 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);
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 "";
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) 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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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",
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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
// 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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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 ên (ne jêgirtinan) 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));
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
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 jêrê 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.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
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.");
});
}
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "\n* {{" + name + "}}");
$(this).val("");
if (!isMobile) sel.trigger("chosen:updated");
ta.focus();
});
wrap.append(sel).append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify chosen widgets of the programmatic value change.
if (!isMobile) fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>"));
$.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);
if (isMobile) {
populateMenus(input, step.keys, getDisabledKeys(step));
// On mobile return the wrapper; the select is inside it.
// Change events still fire on the inner select.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
}
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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
if (isMobile) {
stepDiv.append(wrapNativeSelect(input));
} else {
stepDiv.append(input);
}
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
if (!isMobile) stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
if (!isMobile) $("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
framed: false
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
if (!isMobile) startForm.find("select").chosen(chosenOptions);
refresh();
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Pinned choices come from MediaWiki:Gadget-start.js/data.json (langChoices),
// already loaded into steps.lang.choices before build() runs.
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice.code; });
zimanJsonPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = { code: code, label: name, sortKey: sortKey };
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey.
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
var langMenu = $("#wiktku-start-lang-field");
populateMenus(langMenu, steps.lang.choices);
langMenu.trigger("chosen:updated");
var transMenus = $("#wiktku-start-trans .wiktku-start-step-keyfield");
populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
transMenus.trigger("chosen:updated");
var dundeMenus = $("#wiktku-start-dunde .wiktku-start-step-keyfield");
populateMenus(dundeMenus, steps.dunde.keys, getDisabledKeys(steps.dunde));
dundeMenus.trigger("chosen:updated");
var zaravaMenus = $("#wiktku-start-zarava .wiktku-start-step-keyfield");
populateMenus(zaravaMenus, steps.zarava.keys, getDisabledKeys(steps.zarava));
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page (if the "Li
// cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
});
}
// jquery.chosen/oojs-ui-core load in parallel with metaApiPromise above
// (mediawiki.api + the two API requests) — this block only needs to wait
// on the UI modules itself.
mw.loader.using(["jquery.chosen", "oojs-ui-core"], function () {
// Load metadata, langChoices, posChoices and tplDefs from the companion
// data page so they can be updated without touching this script.
// (Request was already fired via metaApiPromise above, in parallel
// with this module load — we're just picking up the result here.)
metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Populate lang choices (pinned list used by build() → ziman.json loader).
// data.json stores these as a plain {code: label} object.
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// 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.dfn.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 (now populated) lang choices.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
}).always(build);
});
});
//</nowiki>
8unyik6xvvsghe5h9oumevrqsr53269
6291592
6291591
2026-07-26T19:00:52Z
Wikihez
7344
6291592
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
function wrapNativeSelect(select) {
var placeholder = select.attr("data-placeholder") || "Lê bigere…";
// Hide the native select — we drive it programmatically.
select.hide();
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results'></ul>").hide();
// Full option cache — rebuilt when chosen:updated fires.
var allOptions = [];
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
allOptions.push({
value: v,
text: $(this).text(),
disabled: $(this).prop("disabled")
});
});
}
cacheOptions();
function showResults(query) {
var q = query.trim().toLowerCase();
resultsList.empty();
var matched;
if (q) {
matched = [];
$.each(allOptions, function (_, o) {
if (o.disabled) return;
var codeMatch = o.value.toLowerCase().indexOf(q) !== -1;
var nameMatch = o.text.toLowerCase().indexOf(q) !== -1;
if (codeMatch || nameMatch) {
matched.push({ opt: o, codeMatch: codeMatch });
}
});
// Code matches (especially prefix matches) come first.
matched.sort(function (a, b) {
var aCode = a.opt.value.toLowerCase();
var bCode = b.opt.value.toLowerCase();
var aPrefix = aCode.indexOf(q) === 0;
var bPrefix = bCode.indexOf(q) === 0;
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
if (a.codeMatch !== b.codeMatch) return a.codeMatch ? -1 : 1;
return 0;
});
matched = matched.map(function (item) { return item.opt; });
} else {
matched = allOptions.filter(function (o) { return !o.disabled; });
}
if (!matched.length) {
resultsList.hide();
return;
}
$.each(matched.slice(0, 50), function (_, opt) {
var li = $("<li></li>").text(opt.text).attr("data-value", opt.value);
// 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);
});
// Keep mousedown for desktop fallback.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
resultsList.append(li);
});
// Position the list above or below depending on available 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%"
});
resultsList.show();
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
resultsList.hide();
select.trigger("change");
}
textInput.on("input focus", function () {
showResults($(this).val());
});
textInput.on("blur", function () {
// Short delay so a touchstart on a result item fires first.
setTimeout(function () { resultsList.hide(); }, 200);
});
// chosen:updated → re-cache (called after ziman.json loads).
select.on("chosen:updated", function () {
cacheOptions();
// If a value is already selected, keep the display text in sync;
// otherwise (e.g. a "pick to insert" dropdown resetting itself
// after use) clear the search box back to blank/placeholder.
var cur = select.val();
if (cur) {
var match = $.grep(allOptions, function (o) { return o.value === cur; })[0];
if (match) textInput.val(match.text);
} else {
textInput.val("");
}
});
// No-op .chosen() so desktop call-sites don't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a plain {code: label} object (the format used for langChoices
// and posChoices in data.json) into the ordered array of choice objects
// ({code, label[, sortKey]}) that the rest of this script works with.
// isLang: true adds a sortKey (defaults to label) and tells populateMenus
// to show "(code)" after the label — used for 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// 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" : ""));
}
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field wiktku-plain-select",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [{ code: code, label: genderLabel(code) }];
});
populateMenus(select, choices, null, "Cinsiyetê hilbijêre");
if ($.grep(choices, function (c) { return c.code === prevVal; }).length) {
select.val(prevVal);
}
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
choices: [
["gh", "gerguhêz"],
['x-gh', 'xwerû-gerguhêz'],
['p-gh', 'pêkhatî-gerguhêz'],
['h-gh', 'hevedudanî-gerguhêz'],
['b-gh', 'biwêjî-gerguhêz'],
["ngh", "negerguhêz"],
['x-ngh', 'xwerû-negerguhêz'],
['p-ngh', 'pêkhatî-negerguhêz'],
['h-ngh', 'hevedudanî-negerguhêz'],
['b-ngh', 'biwêjî-negerguhêz'] ],
output: function () { return ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
var opt = $("<option></option>")
.val(name)
.text(name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
if (!isMobile) tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (key, label) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", key)
.attr("placeholder", label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Apply Chosen to the new block's template selector on desktop.
if (!isMobile) newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: {},
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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));
});
// Notify Chosen (desktop) / the search widget (mobile) that the
// option list has changed.
sel.trigger("chosen:updated");
},
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", "Nimûne: 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("chosen:updated");
ta.focus();
});
if (isMobile) {
wrap.append(wrapNativeSelect(sel));
} else {
wrap.append(sel);
}
wrap.append($("<br>")).append(ta);
stepDiv.append(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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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 wiktku-plain-select" }));
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys, "Zarava hilbijêre");
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);
}
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);
});
});
},
/** 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);
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 "";
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) 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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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",
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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
// 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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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 ên (ne jêgirtinan) 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));
});
// Notify Chosen (desktop) / the search widget (mobile) that the
// option list has changed.
sel.trigger("chosen:updated");
},
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 jêrê 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>");
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.");
});
}
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "\n* {{" + name + "}}");
$(this).val("");
sel.trigger("chosen:updated");
ta.focus();
});
if (isMobile) {
wrap.append(wrapNativeSelect(sel));
} else {
wrap.append(sel);
}
wrap.append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify chosen widgets of the programmatic value change.
if (!isMobile) fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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);
if (isMobile) {
populateMenus(input, step.keys, getDisabledKeys(step));
// On mobile return the wrapper; the select is inside it.
// Change events still fire on the inner select.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
}
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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
if (isMobile) {
stepDiv.append(wrapNativeSelect(input));
} else {
stepDiv.append(input);
}
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
if (!isMobile) stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
if (!isMobile) $("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke"
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
if (!isMobile) startForm.find("select").not(".wiktku-plain-select").chosen(chosenOptions);
refresh();
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Pinned choices come from MediaWiki:Gadget-start.js/data.json (langChoices),
// already loaded into steps.lang.choices before build() runs.
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice.code; });
zimanJsonPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = { code: code, label: name, sortKey: sortKey };
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey.
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
var langMenu = $("#wiktku-start-lang-field");
populateMenus(langMenu, steps.lang.choices);
if (!isMobile) langMenu.chosen("destroy").chosen(chosenOptions);
langMenu.trigger("chosen:updated");
var transMenus = $("#wiktku-start-trans .wiktku-start-step-keyfield");
populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
if (!isMobile) transMenus.chosen("destroy").chosen(chosenOptions);
transMenus.trigger("chosen:updated");
var dundeMenus = $("#wiktku-start-dunde .wiktku-start-step-keyfield");
populateMenus(dundeMenus, steps.dunde.keys, getDisabledKeys(steps.dunde));
if (!isMobile) dundeMenus.chosen("destroy").chosen(chosenOptions);
dundeMenus.trigger("chosen:updated");
var zaravaMenus = $("#wiktku-start-zarava .wiktku-start-step-keyfield");
populateMenus(zaravaMenus, steps.zarava.keys, getDisabledKeys(steps.zarava), "Zarava hilbijêre");
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page (if the "Li
// cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
});
}
// jquery.chosen/oojs-ui-core load in parallel with metaApiPromise above
// (mediawiki.api + the two API requests) — this block only needs to wait
// on the UI modules itself.
mw.loader.using(["jquery.chosen", "oojs-ui-core"], function () {
// Load metadata, langChoices, posChoices and tplDefs from the companion
// data page so they can be updated without touching this script.
// (Request was already fired via metaApiPromise above, in parallel
// with this module load — we're just picking up the result here.)
metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Populate lang choices (pinned list used by build() → ziman.json loader).
// data.json stores these as a plain {code: label} object.
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// 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.dfn.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 (now populated) lang choices.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
}).always(build);
});
});
//</nowiki>
50o6vcgdtskh6qgub3b0cintshz549a
6291593
6291592
2026-07-26T19:12:25Z
Wikihez
7344
6291593
javascript
text/javascript
$(function(){if(window.location.href.includes("editintro=User:Wikihez/creation/intro")){return}var ns=mw.config.get("wgNamespaceNumber");if(ns&&!(ns===2&&mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))){return}var action=mw.config.get("wgAction");var pageExists=!!mw.config.get("wgArticleId");var addLangMode=pageExists&&mw.util.getParamValue("wiktkuaddlang")==="1";if(action==="view"&&pageExists){var targetContainer;var skin=mw.config.get("skin");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","Ji vê rûpelê re ziman an jî beşeke nû zêde bike");return}if(!(action==="edit"||action==="submit")){return}if(!addLangMode&&!$(".mw-newarticletext, .mw-newarticletextanon").length){return}mw.notify("Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",{type:"info",tag:"wiktku-start-notice",autoHide:true,autoHideSeconds:10});var dataJsonPromise,zimanJsonPromise;var metaApiPromise=mw.loader.using("mediawiki.api").then(function(){var metaApi=new mw.Api;dataJsonPromise=metaApi.get({action:"query",prop:"revisions",titles:"MediaWiki:Gadget-start.js/data.json",rvprop:"content",rvslots:"main",formatversion:2});zimanJsonPromise=metaApi.get({action:"query",prop:"revisions",titles:"MediaWiki:Gadget-translation_editor.js/ziman.json",rvprop:"content",rvslots:"main",formatversion:2});return dataJsonPromise});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 langHeaderSource(codePattern){return"==\\s*\\{\\{\\s*ziman\\s*\\|\\s*("+codePattern+")\\s*\\}\\}\\s*=="}function findLangHeader(text,code){var m=new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);return m?{index:m.index,match:m[0]}:null}function stripWikilinks(str){return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g,function(m,target,display){return display||target})}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 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){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 isMobile=!!mw.config.get("wgMFMode")||window.matchMedia("(pointer: coarse)").matches;var chosenOptions={width:"20em",disable_search_threshold:0,placeholder_text_single:"Yekî hilbijêre",placeholder_text_multiple:"Hilbijêre",no_results_text:"Nehat dîtin",enable_split_word_search:true,search_contains:true};function wrapNativeSelect(select){var placeholder=select.attr("data-placeholder")||"Lê bigere…";select.hide();var wrapper=$("<span class='wiktku-native-select-wrap'></span>");var textInput=$("<input type='search' autocomplete='off'>").attr("placeholder",placeholder).addClass("wiktku-start-narrow wiktku-ac-input");var resultsList=$("<ul class='wiktku-ac-results'></ul>").hide();var allOptions=[];function cacheOptions(){allOptions=[];select.find("option").each(function(){var v=$(this).val();if(!v)return;allOptions.push({value:v,text:$(this).text(),disabled:$(this).prop("disabled")})})}cacheOptions();function showResults(query){var q=query.trim().toLowerCase();resultsList.empty();var matched;if(q){matched=[];$.each(allOptions,function(_,o){if(o.disabled)return;var codeMatch=o.value.toLowerCase().indexOf(q)!==-1;var nameMatch=o.text.toLowerCase().indexOf(q)!==-1;if(codeMatch||nameMatch){matched.push({opt:o,codeMatch:codeMatch})}});matched.sort(function(a,b){var aCode=a.opt.value.toLowerCase();var bCode=b.opt.value.toLowerCase();var aPrefix=aCode.indexOf(q)===0;var bPrefix=bCode.indexOf(q)===0;if(aPrefix!==bPrefix)return aPrefix?-1:1;if(a.codeMatch!==b.codeMatch)return a.codeMatch?-1:1;return 0});matched=matched.map(function(item){return item.opt})}else{matched=allOptions.filter(function(o){return!o.disabled})}if(!matched.length){resultsList.hide();return}$.each(matched.slice(0,50),function(_,opt){var li=$("<li></li>").text(opt.text).attr("data-value",opt.value);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();selectOption(opt.value,opt.text)});li.on("mousedown",function(e){e.preventDefault();selectOption(opt.value,opt.text)});resultsList.append(li)});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));resultsList.css({"max-height":maxH+"px",top:spaceBelow>=spaceAbove?"100%":"auto",bottom:spaceBelow>=spaceAbove?"auto":"100%"});resultsList.show()}function selectOption(value,text){textInput.val(text);select.val(value);resultsList.hide();select.trigger("change")}textInput.on("input focus",function(){showResults($(this).val())});textInput.on("blur",function(){setTimeout(function(){resultsList.hide()},200)});select.on("chosen:updated",function(){cacheOptions();var cur=select.val();if(cur){var match=$.grep(allOptions,function(o){return o.value===cur})[0];if(match)textInput.val(match.text)}else{textInput.val("")}});select.chosen=function(){return select};wrapper.append(textInput).append(resultsList).append(select);return wrapper}var langsByCode={};var metadata={};var ZARAVA_LANG_CODES=["ckb","sdh","lki","hac","zza"];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"}var GENDER_BASE_LABELS={};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}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)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}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#* {{bênimûne|"+lang+"}}"}return $.map(nimunes,function(n){var text=typeof n==="object"?n.text:n;if(!text)return undefined;var bolded=text.replace(new RegExp("("+pageNameRe+"([^( |,|;|\\?|\\.|\\!)]+)?)","ig"),"'''$1'''");var tpl="\n#: {{nm|"+lang+"|"+bolded;if(typeof n==="object"){if(n.tr)tpl+="|tr="+n.tr;if(n.t)tpl+="|"+n.t}if(text.length<200&&lang!=="ku")tpl+="|rêz=1";tpl+="}}";return tpl}).join("")}function formatJeder(jederList,lang){if(!jederList||!jederList.length)return"";return $.map(jederList,function(jeder){if(!jeder||!jeder.quote)return undefined;if(jeder.jederTpl){var cite="\n#* {{"+jeder.jederTpl+"|"+lang;cite+="|jêgirtin="+jeder.quote;if(jeder.quoteTr)cite+="|tr="+jeder.quoteTr;if(jeder.quoteT)cite+="|tercime="+jeder.quoteT;$.each(jeder.jêderFields||{},function(key,val){if(val)cite+="|"+key+"="+val});cite+="}}";return cite}var out="\n#* {{jêder|"+lang+"|jêgirtin="+jeder.quote;if(jeder.quoteTr)out+="|tr="+jeder.quoteTr;if(jeder.quoteT)out+="|tercime="+jeder.quoteT;var srcParam=jeder.jederCatTpl?"{{"+jeder.jederCatTpl+"}}":jeder.jederManual?jeder.jederManual:"çavkanî?";out+=(srcParam.indexOf("=")!==-1?"|2=":"|")+srcParam;out+="}}";return out}).join("")}function senseHasJeder(sense){return!!(sense.jeder&&$.grep(sense.jeder,function(j){return j&&j.quote}).length>0)}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}function buildCite(sense,lang){return formatNimune(sense.nimune,lang,senseHasJeder(sense))+formatJeder(sense.jeder,lang)}function makeWordListStep(id,name,question_fmt,header,templateName){return{id:id,name:name,question_fmt:question_fmt,multiple:true,narrow:true,visible:function(values){return values.lang&&values.pos},output:function(values){var words=$.grep(values[id]||[],function(w){return!!w});if(!words.length)return"";var lang=values.lang;var section="\n==== "+header+" ====\n";if(words.length===1){return section+"* {{"+templateName+"|"+lang+"|"+words[0]+"}}\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?',question_br:true,choices:[],output:function(values){return"== {{ziman|"+values.lang+"}} ==\n"}},pron:{id:"pron",name:"bilêvkirin",question_fmt:'"'+pageName+'" çawa tê telafûzkirin?',pronTpls:{},visible:function(values){if(!values.lang)return false;if(addLangMode){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},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){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":""))}});if(!isMobile)sel.trigger("chosen:updated")},build:function(stepDiv){if(stepDiv.find(".wiktku-pron-wrap").length)return null;var wrap=$("<div class='wiktku-pron-wrap'></div>");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}}");var sel=$("<select class='wiktku-tpl-insert-select'></select>");sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));var lang=getValues().lang||"";$.each(steps.pron.pronTpls,function(name,tpl){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||"";var selectedOption=sel.find("option:selected");var hasNoLang=selectedOption.attr("data-nolang")==="1";if(!hasNoLang){snippet=snippet.replace(/CODE/g,lang).replace(/SOURCELANG/g,"?")}insertAtCursor(ta[0],snippet);$(this).val("");ta.focus()});wrap.append(sel).append($("<br>")).append(ta);stepDiv.append(wrap);return ta},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.charAt(0)==="*"?line:"* "+line}).join("\n");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},question_br:true,choices:[],output:function(values){var pos=values.pos||"";var cinsiyet=values.cinsiyet||"";var lekTur=values.lektur||"";var capitalizedPos=capitalize(pos);var outputString="\n=== "+capitalizedPos+" ===\n";if(!pageName.includes(" ")&&cinsiyet&&GENDER_BASE_LABELS[cinsiyet]&&(pos==="navdêr"||pos==="serenav")&&values.lang==="ku"){outputString+="{{ku-tewîn-nav|"+pageName+"|"+GENDER_BASE_LABELS[cinsiyet];if(pos==="serenav")outputString+="|y";outputString+="}}\n"}outputString+="{{"+pos+"|"+values.lang;if(pos==="navdêr"||pos==="serenav"){if(cinsiyet){outputString+="|c="+cinsiyet}else if(values.lang==="ku"){outputString+="|c=?"}}else if(/^lêker/.test(pos)&&lekTur){outputString+="|c="+lekTur}if(values.tr){outputString+="|tr="+values.tr}outputString+="}}\n";return outputString}},cinsiyet:{id:"cinsiyet",name:"cinsiyet",question_fmt:'Cinsiyeta "'+pageName+'" çi ye?',question_br:true,visible:function(values){var meta=metadata[values.lang];return!!(values.lang&&(values.pos==="navdêr"||values.pos==="serenav")&&meta&&meta.g&&meta.g.length)},build:function(stepDiv,values){var select=stepDiv.find("select.wiktku-start-step-field");if(!select.length){select=$(mw.html.element("select",{"class":"wiktku-start-step-field wiktku-plain-select",id:"wiktku-start-cinsiyet-field"}));stepDiv.append(select)}if(!values)return select;var meta=metadata[values.lang];var codes=meta&&meta.g||[];var prevVal=select.val();var choices=$.map(codes,function(code){return[{code:code,label:genderLabel(code)}]});populateMenus(select,choices,null,"Cinsiyetê hilbijêre");if($.grep(choices,function(c){return c.code===prevVal}).length){select.val(prevVal)}return select},output:function(){return""}},lektur:{id:"lektur",name:"Cureyê lêkerê (fiîlê)",question_fmt:"Cureyê lêkerê (fiîlê) çi ye?",question_br:true,visible:function(values){return values.lang&&values.pos&&/^lêker/.test(values.pos)},choices:[["gh","gerguhêz"],["x-gh","xwerû-gerguhêz"],["p-gh","pêkhatî-gerguhêz"],["h-gh","hevedudanî-gerguhêz"],["b-gh","biwêjî-gerguhêz"],["ngh","negerguhêz"],["x-ngh","xwerû-negerguhêz"],["p-ngh","pêkhatî-negerguhêz"],["h-ngh","hevedudanî-negerguhêz"],["b-ngh","biwêjî-negerguhêz"]],output:function(){return""}},tr:{id:"tr",name:"transkrîpsiyon",question_fmt:"Transkrîpsiyona kelîmeyê",multiple:false,narrow:true,visible:function(values){return values.lang&&values.pos&&needsTransliteration(values.lang)}},dfn:{id:"dfn",name:"Mane",question_fmt:"Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",multiple:true,ordered:true,rows:3,visible:function(values){return values.lang&&values.pos},example:function(values){var examples=steps.dfn.posExamples||{};var full=values.pos||"";return examples[full]||""},buildSenseBlock:function(stepDiv){var block=$("<li class='wiktku-dfn-sense'></li>");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","Mane…");dfnArea.on("input",function(){steps.dfn.onDfnInput(stepDiv,block,this)});var tplDefs=steps.dfn.tplDefs||{};var _allTplOptions=[];var tplSelect=$(mw.html.element("select",{"class":"wiktku-dfn-tpl-select"}));tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));$.each(tplDefs,function(name,tpl){var opt=$("<option></option>").val(name).text(name).attr("data-langs",tpl.langs?tpl.langs.join(","):"").attr("data-nolang",tpl.noLang?"1":"");tplSelect.append(opt);_allTplOptions.push(opt[0])});function updateTplOptions(){var lang=getValues().lang||"";var currentVal=tplSelect.val();var currentStillValid=false;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}});if(!isMobile)tplSelect.trigger("chosen:updated");if(currentVal&&!currentStillValid){tplSelect.val("");dfnArea.show();tplTitleInput.hide().val("")}}tplSelect.data("updateOptions",updateTplOptions);var tplTitleInput=$("<input type='text'>").addClass("wiktku-dfn-tpl-title wiktku-start-narrow").attr("placeholder","Navê rûpelê…").hide();tplTitleInput.on("input",function(){steps.dfn.onDfnInput(stepDiv,block,this)});function updateFieldVisibility(){var isTpl=!!tplSelect.val();$.each(HIDDEN_ON_TPL_GROUPS,function(_,cls){block.find("."+cls+"-group").toggle(!isTpl)})}block.data("updateFieldVisibility",updateFieldVisibility);tplSelect.on("change",function(){var chosen=$(this).val();if(chosen){dfnArea.hide();tplTitleInput.show().focus()}else{dfnArea.show();tplTitleInput.hide().val("")}updateFieldVisibility();refresh()});var tplRow=$("<div></div>").addClass("wiktku-dfn-tpl-row").append(tplSelect).append(" ").append(tplTitleInput);block.append(tplRow);block.append(dfnArea);updateTplOptions();var allGroups=[{cls:"wiktku-dfn-etiket",label:"Etîket",rows:1,narrow:true,izah:"xisûsen, Mêrdîn, anatomî, Badînî"},{cls:"wiktku-dfn-kategori",label:"Kategorî",rows:1,narrow:true,izah:"wekî etîket lê xuya nabe tenê kategorîze dike"},{cls:"wiktku-dfn-alternativ",label:"Formên alternatîv",rows:1,narrow:true,izah:"Varyantên vê kelîmeyê"},{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-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"},{cls:"wiktku-dfn-jornav",label:"Jornav",rows:1,narrow:true,izah:"Heywan jornavê guhandar e"},{cls:"wiktku-dfn-jernav",label:"Jêrnav",rows:1,narrow:true,izah:"Guhandar jêrnavê heywan e"},{cls:"wiktku-dfn-nimune",label:"Nimûne",rows:2,narrow:false}];$.each(allGroups,function(_,g){var groupDiv=$("<div></div>").addClass("wiktku-dfn-subgroup "+g.cls+"-group");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(){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","Transkrîpsiyona 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 kurmancî"))}if(subWrap.children().length)li.append(subWrap);return li}if(g.cls==="wiktku-dfn-nimune"){var lbl=$("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label+": ");var list=$("<ol></ol>").addClass("wiktku-dfn-sublist "+g.cls+"-list");groupDiv.data("buildNimuneLi",buildNimuneLi);list.append(buildNimuneLi());groupDiv.append(lbl).append(list);block.append(groupDiv)}else{var lbl=$("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label+": "+g.izah);var list=$("<ol></ol>").addClass("wiktku-dfn-sublist "+g.cls+"-list");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",g.label+"…");firstInput.on("input",function(){steps.dfn.onSubInput(list,this,g.rows,g.narrow)});list.append($("<li></li>").append(firstInput));groupDiv.append(lbl).append(list);block.append(groupDiv)}});(function(){var jederGroupDiv=$("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");var jederLbl=$("<span></span>").addClass("wiktku-dfn-sublabel").text("Jêgirtin: ");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");var dragHandle=$("<span></span>").addClass("wiktku-jeder-drag-handle").attr("title","Bişkûne û bikişîne da ku rêzê biguhezîne");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(){var val=$.trim($(this).val());var isLast=li.is(jederList.children("li:last-child"));if(val&&isLast){jederList.append(buildJederLi())}});li.append(dragHandle).append(quoteTa);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","Transkrîpsiyona 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);var citoidWrap=$("<div></div>").addClass("wiktku-dfn-quote-citoid");var citoidInput=$("<input type='text'>").addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow").attr("placeholder","URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");var citoidBtn=new OO.ui.ButtonWidget({label:"Bi Citoid bîne",classes:["wiktku-dfn-quote-citoid-btn"]});var citoidStatus=$("<span></span>").addClass("wiktku-dfn-quote-citoid-status");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.citoidHelper.ensure().then(function(){return 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__";if(srcSel.val()!==targetVal){srcSel.val(targetVal).trigger("change")}if(hasTpl){$.each(result.fields,function(key,val){var input=srcFields.find("[data-field-key='"+key+"']");if(input.length&&!$.trim(input.val())){input.val(val)}})}else{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){e.preventDefault();runCitoidFetch()}});citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);var srcSel=$("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));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))});var srcFields=$("<div></div>").addClass("wiktku-dfn-quote-src-fields");srcSel.on("change",function(){srcFields.empty();var chosen=$(this).val();if(!chosen)return;if(chosen==="__manual__"){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__"){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","Navê şablonê lê 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…");function showCatResults(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()}function selectCatTpl(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;$.each(def.fields,function(key,label){var row=$("<div></div>").addClass("wiktku-dfn-quote-src-row");var fieldLbl=$("<label></label>").text(label+": ");var inp=$("<textarea></textarea>").attr("rows",2).addClass("wiktku-dfn-quote-src-input wiktku-start-narrow").attr("data-field-key",key).attr("placeholder",label+"…");row.append(fieldLbl).append(inp);srcFields.append(row)})});li.append(citoidWrap).append(srcSel).append(srcFields);return li}jederGroupDiv.data("buildJederLi",buildJederLi);jederList.append(buildJederLi());mw.loader.using("jquery.ui.sortable",function(){jederList.sortable({handle:".wiktku-jeder-drag-handle",axis:"y",items:"li:not(:last-child)",tolerance:"pointer"})});jederGroupDiv.append(jederLbl).append(jederList);block.append(jederGroupDiv)})();updateFieldVisibility();return block},onDfnInput:function(stepDiv,block,textarea){var val=$.trim($(textarea).val());var list=stepDiv.find("ol.wiktku-dfn-list");var isLast=block.is(list.children("li:last-child"));if(val&&isLast){var newBlock=steps.dfn.buildSenseBlock(stepDiv);list.append(newBlock);if(!isMobile)newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions)}refresh()},onSubInput:function(list,input,rows,narrow){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)});list.append($("<li></li>").append(next))}},tplDefs:{},posExamples:{},jederDefs:{},build:function(stepDiv){if(stepDiv.find("ol.wiktku-dfn-list").length)return null;var list=$("<ol class='wiktku-dfn-list'></ol>");list.append(steps.dfn.buildSenseBlock(stepDiv));stepDiv.append(list);return null},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()||"";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}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}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__"){}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}senses.push({tpl:tpl,tplNoLang:tplNoLang,tplTitle:tplTitle,mane:mane,etiket:readList("wiktku-dfn-etiket"),kategori:readList("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")})});return senses},getOutput:function(values,callback){var stepDiv=$("#wiktku-start-dfn");var senses=steps.dfn.getSenses(stepDiv);var lang=values.lang;var hasMane=senses.some(function(s){return s.mane!==""||s.tpl&&s.tplTitle||senseHasExtraContent(s)});var hasRegularMane=senses.some(function(s){return!s.tpl&&s.mane!==""});var defLines=[];var citeLines=[];$.each(senses,function(_,sense){if(sense.tpl){if(!sense.tplTitle)return;var tplKatTpl=buildSenseTpl(sense,"kategori",lang,"K");var tplLabelTpl=buildSenseTpl(sense,"etiket",lang,"e");var tplCall=sense.tplNoLang?"{{"+sense.tpl+"|"+sense.tplTitle+"}}":"{{"+sense.tpl+"|"+lang+"|"+sense.tplTitle+"}}";var idx=defLines.length;var cite=buildCite(sense,lang);defLines.push("# "+(tplKatTpl?tplKatTpl+" ":"")+(tplLabelTpl?tplLabelTpl+" ":"")+tplCall+(cite?"{{wiktku-cite-"+idx+"}}":""));citeLines.push(cite);return}var dfn=sense.mane;if(!dfn&&!senseHasExtraContent(sense))return;if(dfn){dfn=stripWikilinks(dfn);if(dfn[0]!==dfn[0].toUpperCase())dfn=capitalize(dfn);if(!dfn.match(/[.…?!)\]]$/))dfn+="."}else{dfn="{{mane?|"+lang+"}}"}var katTpl=buildSenseTpl(sense,"kategori",lang,"K");var labelTpl=buildSenseTpl(sense,"etiket",lang,"e");var out="# "+(katTpl?katTpl+" ":"")+(labelTpl?labelTpl+" ":"")+dfn;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);var cite=buildCite(sense,lang);var idx=defLines.length;defLines.push(out+(cite?"{{wiktku-cite-"+idx+"}}":""));citeLines.push(cite)});function restoreCites(t){return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g,function(_,n){return citeLines[parseInt(n,10)]||""})}var text=defLines.join("\n")+"\n";if(!hasMane){text="# {{mane?|"+lang+"}}\n#* {{bênimûne|"+lang+"}}\n";callback(text);return}if(!hasRegularMane){callback(restoreCites(text));return}var didTryToLoad=function(){if(typeof $.wiktviLinkify==="function"){$.wiktviLinkify(text,function(linked){callback(restoreCites(linked))})}else{callback(restoreCites(text))}};mw.loader.using("ext.gadget.linkify",didTryToLoad,didTryToLoad)}},je:makeWordListStep("je","jê","Kelîmeyên jê çêbûyî binivîse","Jê","g"),tekildar:makeWordListStep("tekildar","têkildar","Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse","Têkildar","g"),binereherwiha:makeWordListStep("binereherwiha","binêre herwiha","Kelîmeyên din ên eleqedar binivîse","Binêre herwiha","g"),etym:{id:"etym",name:"etîmolojî",question_fmt:"Etîmolojiya kelîmeyê binivîse",etymTpls:{},visible:function(values){return values.lang&&values.pos},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))});sel.trigger("chosen:updated")},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","Nimûne: Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");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("chosen:updated");ta.focus()});if(isMobile){wrap.append(wrapNativeSelect(sel))}else{wrap.append(sel)}wrap.append($("<br>")).append(ta);stepDiv.append(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"}var dfnSenses=steps.dfn.getSenses($("#wiktku-start-dfn"));var hasRegularMane=dfnSenses.some(function(s){return!s.tpl&&(s.mane!==""||senseHasExtraContent(s))});if(!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:{navz:true},buildRow:function(stepDiv){var row=$("<li class='wiktku-dunde-row wiktku-lang-row'></li>");var langSel=$(mw.html.element("select",{"class":"wiktku-start-step-keyfield wiktku-start-narrow"}));langSel.attr("data-placeholder","Zimanekî lê bigere");populateMenus(langSel,steps.dunde.keys||[],steps.dunde.disabledKeys);langSel.on("change",function(){steps.dunde.onLangChange(stepDiv,row)});var wordList=$("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");var firstWord=steps.dunde.makeWordInput();wordList.append($("<li></li>").append(firstWord));if(isMobile){row.append(wrapNativeSelect(langSel)).append(wordList)}else{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);if(!isMobile)newRow.find("select").chosen(chosenOptions)}steps.dunde.syncDisabledKeys(stepDiv)},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("chosen:updated")})},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);if(!isMobile)list.find("select").chosen(chosenOptions);return null},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",visible:function(values){return steps.trans.visible(values)},disabledKeys:{navz:true},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 wiktku-plain-select"}));populateMenus(langSel,steps.zarava.keys||[],steps.zarava.disabledKeys,"Zarava hilbijêre");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)}steps.zarava.syncDisabledKeys(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)})})},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);return group},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},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},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"";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)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");if(!wordLines)return undefined;var block="{{werger-ser|"+(gloss.text||"?")+"|"+gloss.index+"}}";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",visible:function(values){if(!(values.lang==="ku"&&values.pos))return false;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},buildRow:function(group){var row=$("<li class='wiktku-trans-row wiktku-lang-row'></li>");var langSel=$(mw.html.element("select",{"class":"wiktku-start-step-keyfield wiktku-start-narrow"}));langSel.attr("data-placeholder","Zimanekî lê bigere");populateMenus(langSel,steps.trans.keys||[],steps.trans.disabledKeys);langSel.on("change",function(){steps.trans.onLangChange(group,row)});var wordList=$("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");var firstWord=steps.trans.makeWordInput();wordList.append($("<li></li>").append(firstWord));if(isMobile){row.append(wrapNativeSelect(langSel)).append(wordList)}else{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);if(!isMobile)newRow.find("select").chosen(chosenOptions)}steps.trans.syncDisabledKeys(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("chosen:updated")})},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);if(!isMobile)list.find("select").chosen(chosenOptions);return group},build:function(stepDiv){var glosses=steps.trans.glossList();if(!glosses.length){stepDiv.find(".wiktku-trans-group").remove();return null}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);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},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},getTranslations:function(stepDiv){var result=[];stepDiv.find(".wiktku-trans-group").each(function(){result.push(steps.trans.getGroupTranslations($(this)))});return result},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}var dfn=s.mane;if(!dfn){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();if(!glosses.length)return"";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):[];if(!gloss.text&&!rows.length)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==== Werger ====\n"+blocks.join("\n")+"\n"}},cavkani:{id:"cavkani",name:"çavkanî",question_fmt:"Çavkaniyên din ên (ne jêgirtinan) lê zêde bike",visible:function(values){return!!values.pos},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))});sel.trigger("chosen:updated")},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 jêrê hilbijêre an rasterast binivîse…");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>");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.")})}sel.on("change",function(){var name=$(this).val();if(!name)return;insertAtCursor(ta[0],"\n* {{"+name+"}}");$(this).val("");sel.trigger("chosen:updated");ta.focus()});if(isMobile){wrap.append(wrapNativeSelect(sel))}else{wrap.append(sel)}wrap.append(status).append($("<br>")).append(ta);stepDiv.append(wrap);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);var ordered_steps=[steps.lang,steps.pron,steps.pos,steps.cinsiyet,steps.lektur,steps.tr,steps.dfn,steps.je,steps.tekildar,steps.etym,steps.dunde,steps.binereherwiha,steps.zarava,steps.trans,steps.cavkani];var PER_SECTION_STEP_IDS=["pos","cinsiyet","lektur","tr","dfn","je","tekildar","etym","dunde","binereherwiha","zarava","trans","cavkani"];var sections=[{}];var activeSection=0;function snapshotDfn(){return steps.dfn.getSenses($("#wiktku-start-dfn"))}function restoreDfn(senses){var stepDiv=$("#wiktku-start-dfn");stepDiv.find("ol.wiktku-dfn-list").remove();steps.dfn.build(stepDiv);var list=stepDiv.find("ol.wiktku-dfn-list");$.each(senses,function(i,sense){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()}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()}else{block.find("textarea.wiktku-dfn-mane").val(sense.mane||"").show();block.find("input.wiktku-dfn-tpl-title").hide().val("")}var updateVis=block.data("updateFieldVisibility");if(updateVis)updateVis();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;$.each(values,function(j,val){var items=subList.find("li");if(j<items.length){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)});subList.append($("<li></li>").append(ta))}})}var lazyMap={"wiktku-dfn-etiket":sense.etiket,"wiktku-dfn-kategori":sense.kategori,"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)});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);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)});if(buildFn)nimuneList.append(buildFn())}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);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){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)});if(buildJederFn)jederList.append(buildJederFn())}})}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}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}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()}});sections[activeSection]=snap}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==="trans"){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;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{var extraInput=steps.trans.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.trans.makeWordInput()))}})});return}if(id==="zarava"){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;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"){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{var extraInput=steps.dunde.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.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)}if(!isMobile)fields.filter("select").trigger("chosen:updated")})}function getWikitextForCurrentSection(callback){var values=getValues();var entry=steps.pos.output(values);steps.dfn.getOutput(values,function(output){entry+=output;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);callback(entry,values)})}function getWikitextAllSections(callback){saveCurrentSection();var results=new Array(sections.length);var sharedValues=getValues();function processSection(i){if(i>=sections.length){restoreSection(sections[activeSection]);refresh();callback(results,sharedValues);return}restoreSection(sections[i]);refresh();getWikitextForCurrentSection(function(sectionText){results[i]=sectionText;processSection(i+1)})}processSection(0)}function getWikitext(callback){getWikitextAllSections(function(sectionTexts,sharedValues){var entry=steps.lang.output(sharedValues);entry+=steps.pron.output(sharedValues);entry+=sectionTexts.join("\n");var dfnSenses=sections[0]&§ions[0].dfn||[];var dfnSummary=dfnSenses.map(function(s){return s.mane}).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;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){var displayText=capitalize(choice.label);if(choice.sortKey!==undefined){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]})}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);if(isMobile){populateMenus(input,step.keys,getDisabledKeys(step));return wrapNativeSelect(input)}input.on("chosen:showing_dropdown",function(){$(this).data("wiktku-defaultValue",$(this).val())});input.change(function(_,context){var inputs=$("#wiktku-start-"+step.id+" .wiktku-start-step-keyfield").not(this);var oldValue=$(this).data("wiktku-defaultValue");var newValue=context.selected;inputs.find("option[value='"+oldValue+"']").prop("disabled",false);inputs.find("option[value='"+newValue+"']").prop("disabled",true);inputs.trigger("chosen:updated")});populateMenus(input,step.keys,getDisabledKeys(step));return input}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);nextInput.on("input",function(evt){refreshForTextField(evt.target,step)});var nextKeyInput=buildKeyInput(step);var nextItem=$("<li></li>").append(nextKeyInput).append(nextInput);item.parent().append(nextItem);if(nextKeyInput)nextKeyInput.chosen(chosenOptions)}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)}function refresh(evt){var values=getValues();var changedStep=evt&&evt.data;var didFocus=false;var canContinue=true;$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function(){var fn=$(this).data("updateOptions");if(typeof fn==="function")fn()});steps.pron.refreshTplSelect();steps.etym.refreshTplSelect();steps.cavkani.refreshTplSelect();(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);var subWrap=li.find(".wiktku-dfn-nimune-sub");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","Transkrîpsiyon…");subWrap.prepend(trTa)}else if(!showTr&&trTa.length){trTa.remove()}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ê kurmancî…");subWrap.append(tTa)}else if(!showT&&tTa.length){tTa.remove()}subWrap=li.find(".wiktku-dfn-nimune-sub");if(subWrap.length&&!subWrap.children().length)subWrap.remove()})})();(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);var subWrap=li.find(".wiktku-dfn-quote-sub");var trTa=li.find(".wiktku-dfn-nimune-quote-tr");if(showTr&&!trTa.length){if(!subWrap.length){subWrap=$("<div></div>").addClass("wiktku-dfn-quote-sub");li.find(".wiktku-dfn-nimune-quote").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","Transkrîpsiyona jêgirtinê…");subWrap.prepend(trTa)}else if(!showTr&&trTa.length){trTa.remove()}var tTa=li.find(".wiktku-dfn-nimune-quote-t");if(showT&&!tTa.length){if(!subWrap.length){subWrap=$("<div></div>").addClass("wiktku-dfn-quote-sub");li.find(".wiktku-dfn-nimune-quote").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ê ya kurmancî…");subWrap.append(tTa)}else if(!showT&&tTa.length){tTa.remove()}subWrap=li.find(".wiktku-dfn-quote-sub");if(subWrap.length&&!subWrap.children().length)subWrap.remove()})})();$.each(steps,function(stepId,step){if(typeof step.visible==="function"&&!step.visible(values)){$("#wiktku-start-"+stepId).slideUp()}else{var stepDiv=$("#wiktku-start-"+stepId);if(changedStep!==step&&typeof step.build==="function"){prepareInput(step.build(stepDiv,values),step)}var input=stepDiv.find(".wiktku-start-step-field");if(typeof step.example==="function"){var exampleText=step.example(values)||(step.id==="dfn"?"Mane…":"");input.attr("placeholder",exampleText)}var didSlideDown;if(!didFocus&&stepDiv.css("display")==="none"&&step.required){didSlideDown=function(evt){input.focus()};didFocus=true}stepDiv.slideDown(didSlideDown)}if(step.required&&(values[stepId]===""||step.multiple&&values[stepId].join("")==="")){canContinue=false}});continueBtn.setDisabled(!canContinue)}function destroy(){$("#wiktku-start-form, .noafterwizard").slideUp(function(){$(this).remove()});$(".noarticletext .nowizard, #editform").slideDown();$("#ca-edit").removeClass("selected");$("#ca-wiktku-nowizard").addClass("selected")}var LANG_PRIORITY=["navz","ku","hac","sdh","lki","ckb","zza"];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){var sep=existing&&!/\n\s*$/.test(existing)?"\n\n":"";return existing+sep+wikitext}var firstSectionStart=firstMatch.index;var lineStart=existing.lastIndexOf("\n",firstSectionStart-1);var preambleEnd=lineStart!==-1?lineStart+1:0;preamble=existing.slice(0,preambleEnd);var body=existing.slice(preambleEnd);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]})}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;return getLangName(a).localeCompare(getLangName(b),userLang)}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);var joined=sections.map(function(s){return s.content.replace(/\n+$/,"")}).join("\n");return preamble+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 di rûpelê de 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(" Naveroka rûpelê paqij bike (rûpelê ji nû ve binivîse)");startForm.append($("<p class='wiktku-addlang-notice'></p>").append(clearPageCheck).append(clearPageLabel));clearPageCheck.on("change",function(){refresh()})}var spinner;var sectionTabsDiv=$("<div id='wiktku-section-tabs'></div>");startForm.submit(function(evt){evt.preventDefault();continueBtn.setDisabled(true);spinner.show();getWikitext(function(wikitext){if(addLangMode&&clearPageCheck&&clearPageCheck.prop("checked")){$("#wpTextbox1").val(wikitext)}else if(addLangMode){var textbox=$("#wpTextbox1");var existing=textbox.val()||"";var langHeaderMatch=wikitext.match(new RegExp("^("+langHeaderSource("[^}]+")+"\\n)"));if(langHeaderMatch){var langHeader=langHeaderMatch[1];var newCode=langHeaderMatch[2];var existingHeader=findLangHeader(existing,newCode);if(existingHeader){var newContent=wikitext.slice(langHeader.length);var searchFrom=existingHeader.index+existingHeader.match.length;var nextL2=existing.indexOf("\n== ",searchFrom);var insertAt=nextL2!==-1?nextL2:existing.length;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{textbox.val(insertNewLangSection(existing,wikitext))}}else{textbox.val(insertNewLangSection(existing,wikitext))}}else{$("#wpTextbox1").val(wikitext)}$("html, body").animate({scrollTop:0});callToAction.text("Hindik ma!");$("#wiktku-preview-container").remove();destroy()})});$.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);stepDiv.append($(mw.html.element("h3",{"class":"wiktku-start-step-name"},new mw.html.Raw(mw.html.element("label",{"for":"wiktku-start-"+step.id+"-field"},name)))));if(step.question_fmt){stepDiv.append($(mw.html.element("label",{"class":"wiktku-start-step-question","for":"wiktku-start-"+step.id+"-field"},step.question_fmt)));if(step.question_br)stepDiv.append($("<br>"))}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);if(isMobile){stepDiv.append(wrapNativeSelect(input))}else{stepDiv.append(input)}}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.multiple){input.on("input",function(evt){refreshForTextField(evt.target,step)});var keyInput=buildKeyInput(step);var list=$(step.ordered?"<ol></ol>":"<ul></ul>").append($("<li></li>").append(keyInput).append(input));stepDiv.append(list)}else stepDiv.append(input)}prepareInput(input,step);stepDiv.hide();if(step.id==="pos")startForm.append(sectionTabsDiv);startForm.append(stepDiv)});var addSectionBtn=new OO.ui.ButtonWidget({label:"+ Beş zêde bike",disabled:true});addSectionBtn.on("click",function(){saveCurrentSection();sections.push({});activeSection=sections.length-1;$.each(PER_SECTION_STEP_IDS,function(_,id){if(id==="dfn"){var stepDiv=$("#wiktku-start-dfn");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==="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}var stepDiv=$("#wiktku-start-"+id);stepDiv.find(".wiktku-start-step-field").val("");if(!isMobile)stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated")});renderSectionTabs();refresh();$("html, body").animate({scrollTop:sectionTabsDiv.offset().top-20},300)});function renderSectionTabs(){addSectionBtn.$element.detach();sectionTabsDiv.empty();var lang=getValues().lang||"";if(!lang){sectionTabsDiv.hide();return}sectionTabsDiv.show();$.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);(function(idx){tab.on("click",function(){if(idx===activeSection)return;saveCurrentSection();activeSection=idx;restoreSection(sections[activeSection]);refresh();renderSectionTabs()})})(i);sectionTabsDiv.append(tab)});var activePos=sections[activeSection]&§ions[activeSection].pos?sections[activeSection].pos:getValues().pos;addSectionBtn.setDisabled(!activePos);sectionTabsDiv.append(addSectionBtn.$element)}renderSectionTabs();function restoreFromCache(cached){if(cached.lang){$("#wiktku-start-lang-field").val(cached.lang);if(!isMobile)$("#wiktku-start-lang-field").trigger("chosen:updated")}refresh();if(cached.pron!==undefined){$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron)}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();mw.notify("Danûstandina berê ya tomarkirî ji cache hate barkirin.",{type:"info",tag:"wiktku-cache-restore"})}startForm.on("change","#wiktku-start-pos-field",function(){renderSectionTabs()});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(){mw.notify("Tê şandin…",{type:"info",tag:"wiktku-submit"});startForm.trigger("submit")});footer.append(continueBtn.$element);var sourceEditorBtn=new OO.ui.ButtonWidget({label:"Edîtora çavkaniyê veke"});sourceEditorBtn.on("click",function(){destroy()});footer.append(sourceEditorBtn.$element);var saveCacheBtn=new OO.ui.ButtonWidget({label:"Li cache tomar bike",title:"Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();var snapshot={lang:getValues().lang||"",pron:$("#wiktku-start-pron .wiktku-pron-text").val()||"",sections:sections,activeSection:activeSection};try{formCache.Set(snapshot);mw.notify("Rewşa formê li cache hate tomarkirin.",{type:"success",tag:"wiktku-cache-save"})}catch(e){mw.notify("Tomarkirina li cache serneket.",{type:"error",tag:"wiktku-cache-save"})}});footer.append(saveCacheBtn.$element);var clearCacheBtn=new OO.ui.ButtonWidget({label:"Cache paqij bike",flags:["destructive"],title:"Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."});clearCacheBtn.on("click",function(){formCache.Set({});mw.notify("Cache hate paqijkirin.",{type:"success",tag:"wiktku-cache-clear"})});footer.append(clearCacheBtn.$element);var previewCheck=$("<input type='checkbox' id='wiktku-preview-toggle'>");var previewLabel=$("<label for='wiktku-preview-toggle'></label>").text(" Pêşdîtina wîkîtekstê");var previewPanel=$("<pre id='wiktku-preview-panel'></pre>").hide();var livePreviewCheck=null;var livePreviewPanel=null;if(!isMobile){livePreviewCheck=$("<input type='checkbox' id='wiktku-live-preview-toggle'>");livePreviewPanel=$("<div id='wiktku-live-preview-panel'></div>").hide()}var previewContainer=$("<div id='wiktku-preview-container'></div>");previewContainer.append(previewPanel);if(!isMobile)previewContainer.append(livePreviewPanel);var previewTimer=null;var livePreviewIntervalId=-1;var livePreviewActive=false;function schedulePreview(){if(!previewCheck.prop("checked"))return;clearTimeout(previewTimer);previewTimer=setTimeout(function(){getWikitext(function(wikitext){previewPanel.text(wikitext).show();updatePanelLayout()})},400)}function loadLivePreview(){getWikitext(function(wikitext){(new mw.Api).parse(wikitext,{title:mw.config.get("wgPageName")}).done(function(html){if(!livePreviewActive)return;livePreviewPanel.html(html).show();updatePanelLayout()})})}function updatePanelLayout(){var wikitextVisible=previewCheck.prop("checked");var liveVisible=livePreviewCheck&&livePreviewCheck.prop("checked");if(wikitextVisible&&liveVisible){previewPanel.css({flex:"1 1 45%","min-width":"280px"});livePreviewPanel.css({flex:"1 1 45%","min-width":"280px"})}else{if(wikitextVisible)previewPanel.css({flex:"1 1 100%","min-width":""});if(liveVisible&&livePreviewPanel)livePreviewPanel.css({flex:"1 1 100%","min-width":""})}}previewCheck.on("change",function(){if($(this).prop("checked")){schedulePreview()}else{clearTimeout(previewTimer);previewPanel.hide();updatePanelLayout()}});if(!isMobile){livePreviewCheck.on("change",function(){if($(this).prop("checked")){livePreviewActive=true;livePreviewPanel.show();loadLivePreview();livePreviewIntervalId=setInterval(loadLivePreview,500)}else{livePreviewActive=false;clearInterval(livePreviewIntervalId);livePreviewIntervalId=-1;livePreviewPanel.hide().empty();updatePanelLayout()}})}startForm.on("input change",function(){schedulePreview()});footer.append($("<span style='margin-left:1em'></span>").append(previewCheck).append(previewLabel));if(!isMobile){var livePreviewLabel=$("<label for='wiktku-live-preview-toggle'></label>").text(" Pêşdîtina zindî");footer.append($("<span style='margin-left:1em'></span>").append(livePreviewCheck).append(livePreviewLabel))}startForm.append(footer);$("#editform").hide().before(startForm);startForm.after(previewContainer);if(!isMobile)startForm.find("select").not(".wiktku-plain-select").chosen(chosenOptions);refresh();var pinnedChoices=steps.lang.choices.slice();var pinnedCodes=$.map(pinnedChoices,function(choice){return choice.code});zimanJsonPromise.then(function(data){var page=data.query.pages[0];var content=page&&page.revisions&&page.revisions[0].slots.main.content;var zimanData;try{zimanData=JSON.parse(content)}catch(e){zimanData={}}var extraChoices=[];$.each(zimanData,function(code,name){if(/-pro$/i.test(code))return;if(/ziman[eê]\s+destan/i.test(name))return;if(pinnedCodes.indexOf(code)!==-1)return;var sortKey=name.replace(/^ziman[eê]\s+/i,"");var choice={code:code,label:name,sortKey:sortKey};extraChoices.push(choice);langsByCode[code]=choice});$.each(pinnedChoices,function(i,choice){if(choice.sortKey===undefined)choice.sortKey=choice.label;langsByCode[choice.code]=choice});extraChoices.sort(function(a,b){return a.sortKey.localeCompare(b.sortKey,userLang)});var allChoices=pinnedChoices.concat(extraChoices);steps.lang.choices=allChoices;steps.trans.keys=allChoices;steps.dunde.keys=allChoices;steps.zarava.keys=getZaravaKeys(allChoices);var langMenu=$("#wiktku-start-lang-field");populateMenus(langMenu,steps.lang.choices);if(!isMobile)langMenu.chosen("destroy").chosen(chosenOptions);langMenu.trigger("chosen:updated");var transMenus=$("#wiktku-start-trans .wiktku-start-step-keyfield");populateMenus(transMenus,steps.trans.keys,getDisabledKeys(steps.trans));if(!isMobile)transMenus.chosen("destroy").chosen(chosenOptions);transMenus.trigger("chosen:updated");var dundeMenus=$("#wiktku-start-dunde .wiktku-start-step-keyfield");populateMenus(dundeMenus,steps.dunde.keys,getDisabledKeys(steps.dunde));if(!isMobile)dundeMenus.chosen("destroy").chosen(chosenOptions);dundeMenus.trigger("chosen:updated");var zaravaMenus=$("#wiktku-start-zarava .wiktku-start-step-keyfield");populateMenus(zaravaMenus,steps.zarava.keys,getDisabledKeys(steps.zarava),"Zarava hilbijêre");var cachedDraft=formCache.Get();if(cachedDraft&&cachedDraft.sections&&cachedDraft.sections.length){restoreFromCache(cachedDraft)}})}mw.loader.using(["jquery.chosen","oojs-ui-core"],function(){metaApiPromise.then(function(data){var page=data.query.pages[0];var content=page&&page.revisions&&page.revisions[0].slots.main.content;var d;try{d=JSON.parse(content)}catch(e){d={}}if(d.metadata)$.extend(metadata,d.metadata);if(d.genderLabels)$.extend(GENDER_BASE_LABELS,d.genderLabels);if(d.langChoices)steps.lang.choices=choicesFromObject(d.langChoices,true);if(d.posChoices)steps.pos.choices=choicesFromObject(d.posChoices,false);if(d.tplDefs)steps.dfn.tplDefs=d.tplDefs;if(d.posExamples)steps.dfn.posExamples=d.posExamples;if(d.jederDefs)steps.dfn.jederDefs=d.jederDefs;if(d.pronTpls){steps.pron.pronTpls=d.pronTpls;steps.pron.refreshTplSelect()}if(d.etymTpls){steps.etym.etymTpls=d.etymTpls;steps.etym.refreshTplSelect()}steps.trans.keys=steps.lang.choices;steps.dunde.keys=steps.lang.choices;steps.zarava.keys=getZaravaKeys(steps.lang.choices)}).always(build)})});
ea3wt1rauq9kiw5zty7tcovdmyk5g44
6291594
6291593
2026-07-26T19:13:49Z
Wikihez
7344
Restored revision 6291592 by [[Special:Contributions/Wikihez|Wikihez]] ([[User talk:Wikihez|talk]]) ([[:m:User:Xiplus/TwinkleGlobal|TwinkleGlobal]])
6291594
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
function wrapNativeSelect(select) {
var placeholder = select.attr("data-placeholder") || "Lê bigere…";
// Hide the native select — we drive it programmatically.
select.hide();
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results'></ul>").hide();
// Full option cache — rebuilt when chosen:updated fires.
var allOptions = [];
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
allOptions.push({
value: v,
text: $(this).text(),
disabled: $(this).prop("disabled")
});
});
}
cacheOptions();
function showResults(query) {
var q = query.trim().toLowerCase();
resultsList.empty();
var matched;
if (q) {
matched = [];
$.each(allOptions, function (_, o) {
if (o.disabled) return;
var codeMatch = o.value.toLowerCase().indexOf(q) !== -1;
var nameMatch = o.text.toLowerCase().indexOf(q) !== -1;
if (codeMatch || nameMatch) {
matched.push({ opt: o, codeMatch: codeMatch });
}
});
// Code matches (especially prefix matches) come first.
matched.sort(function (a, b) {
var aCode = a.opt.value.toLowerCase();
var bCode = b.opt.value.toLowerCase();
var aPrefix = aCode.indexOf(q) === 0;
var bPrefix = bCode.indexOf(q) === 0;
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
if (a.codeMatch !== b.codeMatch) return a.codeMatch ? -1 : 1;
return 0;
});
matched = matched.map(function (item) { return item.opt; });
} else {
matched = allOptions.filter(function (o) { return !o.disabled; });
}
if (!matched.length) {
resultsList.hide();
return;
}
$.each(matched.slice(0, 50), function (_, opt) {
var li = $("<li></li>").text(opt.text).attr("data-value", opt.value);
// 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);
});
// Keep mousedown for desktop fallback.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
resultsList.append(li);
});
// Position the list above or below depending on available 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%"
});
resultsList.show();
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
resultsList.hide();
select.trigger("change");
}
textInput.on("input focus", function () {
showResults($(this).val());
});
textInput.on("blur", function () {
// Short delay so a touchstart on a result item fires first.
setTimeout(function () { resultsList.hide(); }, 200);
});
// chosen:updated → re-cache (called after ziman.json loads).
select.on("chosen:updated", function () {
cacheOptions();
// If a value is already selected, keep the display text in sync;
// otherwise (e.g. a "pick to insert" dropdown resetting itself
// after use) clear the search box back to blank/placeholder.
var cur = select.val();
if (cur) {
var match = $.grep(allOptions, function (o) { return o.value === cur; })[0];
if (match) textInput.val(match.text);
} else {
textInput.val("");
}
});
// No-op .chosen() so desktop call-sites don't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a plain {code: label} object (the format used for langChoices
// and posChoices in data.json) into the ordered array of choice objects
// ({code, label[, sortKey]}) that the rest of this script works with.
// isLang: true adds a sortKey (defaults to label) and tells populateMenus
// to show "(code)" after the label — used for 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// 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" : ""));
}
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field wiktku-plain-select",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [{ code: code, label: genderLabel(code) }];
});
populateMenus(select, choices, null, "Cinsiyetê hilbijêre");
if ($.grep(choices, function (c) { return c.code === prevVal; }).length) {
select.val(prevVal);
}
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
choices: [
["gh", "gerguhêz"],
['x-gh', 'xwerû-gerguhêz'],
['p-gh', 'pêkhatî-gerguhêz'],
['h-gh', 'hevedudanî-gerguhêz'],
['b-gh', 'biwêjî-gerguhêz'],
["ngh", "negerguhêz"],
['x-ngh', 'xwerû-negerguhêz'],
['p-ngh', 'pêkhatî-negerguhêz'],
['h-ngh', 'hevedudanî-negerguhêz'],
['b-ngh', 'biwêjî-negerguhêz'] ],
output: function () { return ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
var opt = $("<option></option>")
.val(name)
.text(name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
if (!isMobile) tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (key, label) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", key)
.attr("placeholder", label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Apply Chosen to the new block's template selector on desktop.
if (!isMobile) newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: {},
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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));
});
// Notify Chosen (desktop) / the search widget (mobile) that the
// option list has changed.
sel.trigger("chosen:updated");
},
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", "Nimûne: 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("chosen:updated");
ta.focus();
});
if (isMobile) {
wrap.append(wrapNativeSelect(sel));
} else {
wrap.append(sel);
}
wrap.append($("<br>")).append(ta);
stepDiv.append(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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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 wiktku-plain-select" }));
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys, "Zarava hilbijêre");
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);
}
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);
});
});
},
/** 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);
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 "";
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) 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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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",
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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
// 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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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 ên (ne jêgirtinan) 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));
});
// Notify Chosen (desktop) / the search widget (mobile) that the
// option list has changed.
sel.trigger("chosen:updated");
},
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 jêrê 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>");
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.");
});
}
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "\n* {{" + name + "}}");
$(this).val("");
sel.trigger("chosen:updated");
ta.focus();
});
if (isMobile) {
wrap.append(wrapNativeSelect(sel));
} else {
wrap.append(sel);
}
wrap.append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify chosen widgets of the programmatic value change.
if (!isMobile) fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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);
if (isMobile) {
populateMenus(input, step.keys, getDisabledKeys(step));
// On mobile return the wrapper; the select is inside it.
// Change events still fire on the inner select.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
}
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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
if (isMobile) {
stepDiv.append(wrapNativeSelect(input));
} else {
stepDiv.append(input);
}
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
if (!isMobile) stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
if (!isMobile) $("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke"
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
if (!isMobile) startForm.find("select").not(".wiktku-plain-select").chosen(chosenOptions);
refresh();
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Pinned choices come from MediaWiki:Gadget-start.js/data.json (langChoices),
// already loaded into steps.lang.choices before build() runs.
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice.code; });
zimanJsonPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = { code: code, label: name, sortKey: sortKey };
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey.
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
var langMenu = $("#wiktku-start-lang-field");
populateMenus(langMenu, steps.lang.choices);
if (!isMobile) langMenu.chosen("destroy").chosen(chosenOptions);
langMenu.trigger("chosen:updated");
var transMenus = $("#wiktku-start-trans .wiktku-start-step-keyfield");
populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
if (!isMobile) transMenus.chosen("destroy").chosen(chosenOptions);
transMenus.trigger("chosen:updated");
var dundeMenus = $("#wiktku-start-dunde .wiktku-start-step-keyfield");
populateMenus(dundeMenus, steps.dunde.keys, getDisabledKeys(steps.dunde));
if (!isMobile) dundeMenus.chosen("destroy").chosen(chosenOptions);
dundeMenus.trigger("chosen:updated");
var zaravaMenus = $("#wiktku-start-zarava .wiktku-start-step-keyfield");
populateMenus(zaravaMenus, steps.zarava.keys, getDisabledKeys(steps.zarava), "Zarava hilbijêre");
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page (if the "Li
// cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
});
}
// jquery.chosen/oojs-ui-core load in parallel with metaApiPromise above
// (mediawiki.api + the two API requests) — this block only needs to wait
// on the UI modules itself.
mw.loader.using(["jquery.chosen", "oojs-ui-core"], function () {
// Load metadata, langChoices, posChoices and tplDefs from the companion
// data page so they can be updated without touching this script.
// (Request was already fired via metaApiPromise above, in parallel
// with this module load — we're just picking up the result here.)
metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Populate lang choices (pinned list used by build() → ziman.json loader).
// data.json stores these as a plain {code: label} object.
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// 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.dfn.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 (now populated) lang choices.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
}).always(build);
});
});
//</nowiki>
50o6vcgdtskh6qgub3b0cintshz549a
6291596
6291594
2026-07-26T19:32:15Z
Wikihez
7344
6291596
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
function wrapNativeSelect(select) {
var placeholder = select.attr("data-placeholder") || "Lê bigere…";
// Hide the native select — we drive it programmatically.
select.hide();
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results'></ul>").hide();
// Full option cache — rebuilt when chosen:updated fires.
var allOptions = [];
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
allOptions.push({
value: v,
text: $(this).text(),
disabled: $(this).prop("disabled")
});
});
}
cacheOptions();
function showResults(query) {
var q = query.trim().toLowerCase();
resultsList.empty();
var matched;
if (q) {
matched = [];
$.each(allOptions, function (_, o) {
if (o.disabled) return;
var codeMatch = o.value.toLowerCase().indexOf(q) !== -1;
var nameMatch = o.text.toLowerCase().indexOf(q) !== -1;
if (codeMatch || nameMatch) {
matched.push({ opt: o, codeMatch: codeMatch });
}
});
// Code matches (especially prefix matches) come first.
matched.sort(function (a, b) {
var aCode = a.opt.value.toLowerCase();
var bCode = b.opt.value.toLowerCase();
var aPrefix = aCode.indexOf(q) === 0;
var bPrefix = bCode.indexOf(q) === 0;
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
if (a.codeMatch !== b.codeMatch) return a.codeMatch ? -1 : 1;
return 0;
});
matched = matched.map(function (item) { return item.opt; });
} else {
matched = allOptions.filter(function (o) { return !o.disabled; });
}
if (!matched.length) {
resultsList.hide();
return;
}
$.each(matched.slice(0, 50), function (_, opt) {
var li = $("<li></li>").text(opt.text).attr("data-value", opt.value);
// 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);
});
// Keep mousedown for desktop fallback.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
resultsList.append(li);
});
// Position the list above or below depending on available 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%"
});
resultsList.show();
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
resultsList.hide();
select.trigger("change");
}
textInput.on("input focus", function () {
showResults($(this).val());
});
textInput.on("blur", function () {
// Short delay so a touchstart on a result item fires first.
setTimeout(function () { resultsList.hide(); }, 200);
});
// chosen:updated → re-cache (called after ziman.json loads).
select.on("chosen:updated", function () {
cacheOptions();
// If a value is already selected, keep the display text in sync;
// otherwise (e.g. a "pick to insert" dropdown resetting itself
// after use) clear the search box back to blank/placeholder.
var cur = select.val();
if (cur) {
var match = $.grep(allOptions, function (o) { return o.value === cur; })[0];
if (match) textInput.val(match.text);
} else {
textInput.val("");
}
});
// No-op .chosen() so desktop call-sites don't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a plain {code: label} object (the format used for langChoices
// and posChoices in data.json) into the ordered array of choice objects
// ({code, label[, sortKey]}) that the rest of this script works with.
// isLang: true adds a sortKey (defaults to label) and tells populateMenus
// to show "(code)" after the label — used for 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// 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" : ""));
}
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field wiktku-plain-select",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [{ code: code, label: genderLabel(code) }];
});
populateMenus(select, choices, null, "Cinsiyetê hilbijêre");
if ($.grep(choices, function (c) { return c.code === prevVal; }).length) {
select.val(prevVal);
}
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
// NOTE: populateMenus() expects {code, label} objects (like every
// other step's choices) — plain [code, label] pairs leave
// choice.code/choice.label undefined, so Chosen (desktop) ends up
// with unusable options.
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 ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
var opt = $("<option></option>")
.val(name)
.text(name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
if (!isMobile) tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (key, label) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", key)
.attr("placeholder", label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Apply Chosen to the new block's template selector on desktop.
if (!isMobile) newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: {},
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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));
});
// Notify Chosen (desktop) / the search widget (mobile) that the
// option list has changed.
sel.trigger("chosen:updated");
},
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", "Nimûne: 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("chosen:updated");
ta.focus();
});
if (isMobile) {
wrap.append(wrapNativeSelect(sel));
} else {
wrap.append(sel);
}
wrap.append($("<br>")).append(ta);
stepDiv.append(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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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: "Eger ev kelîme di zaravayên din de bi awayekî cuda tê gotin an nivîsandin, wan lê zêde bike",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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 wiktku-plain-select" }));
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys, "Zarava hilbijêre");
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);
}
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);
});
});
},
/** 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);
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 "";
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) 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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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: "Wergera 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
if (isMobile) {
row.append(wrapNativeSelect(langSel)).append(wordList);
} else {
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);
// Init chosen after the new row is in the DOM (desktop only).
if (!isMobile) newRow.find("select").chosen(chosenOptions);
}
// 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("chosen:updated");
});
},
// 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);
// Init chosen after the select is in the DOM (desktop only).
if (!isMobile) list.find("select").chosen(chosenOptions);
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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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 ên (ne jêgirtinan) 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));
});
// Notify Chosen (desktop) / the search widget (mobile) that the
// option list has changed.
sel.trigger("chosen:updated");
},
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 jêrê 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>");
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.");
});
}
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "\n* {{" + name + "}}");
$(this).val("");
sel.trigger("chosen:updated");
ta.focus();
});
if (isMobile) {
wrap.append(wrapNativeSelect(sel));
} else {
wrap.append(sel);
// build() is called again for every new beş (see the
// "+ Beş zêde bike" handler), after the initial page-wide
// .chosen() pass has already run — so this select needs
// its own chosen() call or it stays a plain, unsearchable
// native <select> on desktop.
sel.chosen(chosenOptions);
}
wrap.append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify chosen widgets of the programmatic value change.
if (!isMobile) fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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);
if (isMobile) {
populateMenus(input, step.keys, getDisabledKeys(step));
// On mobile return the wrapper; the select is inside it.
// Change events still fire on the inner select.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
}
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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
if (isMobile) {
stepDiv.append(wrapNativeSelect(input));
} else {
stepDiv.append(input);
}
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
if (!isMobile) stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
if (!isMobile) $("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke"
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
if (!isMobile) startForm.find("select").not(".wiktku-plain-select").chosen(chosenOptions);
refresh();
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Pinned choices come from MediaWiki:Gadget-start.js/data.json (langChoices),
// already loaded into steps.lang.choices before build() runs.
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice.code; });
zimanJsonPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = { code: code, label: name, sortKey: sortKey };
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey.
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
var langMenu = $("#wiktku-start-lang-field");
populateMenus(langMenu, steps.lang.choices);
if (!isMobile) langMenu.chosen("destroy").chosen(chosenOptions);
langMenu.trigger("chosen:updated");
var transMenus = $("#wiktku-start-trans .wiktku-start-step-keyfield");
populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
if (!isMobile) transMenus.chosen("destroy").chosen(chosenOptions);
transMenus.trigger("chosen:updated");
var dundeMenus = $("#wiktku-start-dunde .wiktku-start-step-keyfield");
populateMenus(dundeMenus, steps.dunde.keys, getDisabledKeys(steps.dunde));
if (!isMobile) dundeMenus.chosen("destroy").chosen(chosenOptions);
dundeMenus.trigger("chosen:updated");
var zaravaMenus = $("#wiktku-start-zarava .wiktku-start-step-keyfield");
populateMenus(zaravaMenus, steps.zarava.keys, getDisabledKeys(steps.zarava), "Zarava hilbijêre");
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page (if the "Li
// cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
});
}
// jquery.chosen/oojs-ui-core load in parallel with metaApiPromise above
// (mediawiki.api + the two API requests) — this block only needs to wait
// on the UI modules itself.
mw.loader.using(["jquery.chosen", "oojs-ui-core"], function () {
// Load metadata, langChoices, posChoices and tplDefs from the companion
// data page so they can be updated without touching this script.
// (Request was already fired via metaApiPromise above, in parallel
// with this module load — we're just picking up the result here.)
metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Populate lang choices (pinned list used by build() → ziman.json loader).
// data.json stores these as a plain {code: label} object.
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// 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.dfn.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 (now populated) lang choices.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
}).always(build);
});
});
//</nowiki>
7o511tlhrt252arz5vlnd2lttyonbh0
6291611
6291596
2026-07-26T21:27:35Z
Wikihez
7344
6291611
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
function wrapNativeSelect(select) {
var placeholder = select.attr("data-placeholder") || "Lê bigere…";
// Hide the native select — we drive it programmatically.
select.hide();
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results'></ul>").hide();
// Full option cache — rebuilt when chosen:updated fires.
var allOptions = [];
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
allOptions.push({
value: v,
text: $(this).text(),
disabled: $(this).prop("disabled")
});
});
}
cacheOptions();
function showResults(query) {
var q = query.trim().toLowerCase();
resultsList.empty();
var matched;
if (q) {
matched = [];
$.each(allOptions, function (_, o) {
if (o.disabled) return;
var codeMatch = o.value.toLowerCase().indexOf(q) !== -1;
var nameMatch = o.text.toLowerCase().indexOf(q) !== -1;
if (codeMatch || nameMatch) {
matched.push({ opt: o, codeMatch: codeMatch });
}
});
// Code matches (especially prefix matches) come first.
matched.sort(function (a, b) {
var aCode = a.opt.value.toLowerCase();
var bCode = b.opt.value.toLowerCase();
var aPrefix = aCode.indexOf(q) === 0;
var bPrefix = bCode.indexOf(q) === 0;
if (aPrefix !== bPrefix) return aPrefix ? -1 : 1;
if (a.codeMatch !== b.codeMatch) return a.codeMatch ? -1 : 1;
return 0;
});
matched = matched.map(function (item) { return item.opt; });
} else {
matched = allOptions.filter(function (o) { return !o.disabled; });
}
if (!matched.length) {
resultsList.hide();
return;
}
$.each(matched.slice(0, 50), function (_, opt) {
var li = $("<li></li>").text(opt.text).attr("data-value", opt.value);
// 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);
});
// Keep mousedown for desktop fallback.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
resultsList.append(li);
});
// Position the list above or below depending on available 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%"
});
resultsList.show();
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
resultsList.hide();
select.trigger("change");
}
textInput.on("input focus", function () {
showResults($(this).val());
});
textInput.on("blur", function () {
// Short delay so a touchstart on a result item fires first.
setTimeout(function () { resultsList.hide(); }, 200);
});
// chosen:updated → re-cache (called after ziman.json loads).
select.on("chosen:updated", function () {
cacheOptions();
// If a value is already selected, keep the display text in sync;
// otherwise (e.g. a "pick to insert" dropdown resetting itself
// after use) clear the search box back to blank/placeholder.
var cur = select.val();
if (cur) {
var match = $.grep(allOptions, function (o) { return o.value === cur; })[0];
if (match) textInput.val(match.text);
} else {
textInput.val("");
}
});
// No-op .chosen() so desktop call-sites don't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a plain {code: label} object (the format used for langChoices
// and posChoices in data.json) into the ordered array of choice objects
// ({code, label[, sortKey]}) that the rest of this script works with.
// isLang: true adds a sortKey (defaults to label) and tells populateMenus
// to show "(code)" after the label — used for 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// Use the same code/name-priority autocomplete widget as mobile
// on desktop too — jquery.chosen has no way to rank an exact/
// prefix code match (e.g. "ang" → Îngilîziya kevn) above
// unrelated label substring matches, so plain Chosen search
// buried the right result. See wrapNativeSelect's showResults().
forceCustomSearch: true,
// 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" : ""));
}
});
// Notify Chosen that the option list has changed (no-op on mobile).
if (!isMobile) sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field wiktku-plain-select",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [{ code: code, label: genderLabel(code) }];
});
populateMenus(select, choices, null, "Cinsiyetê hilbijêre");
if ($.grep(choices, function (c) { return c.code === prevVal; }).length) {
select.val(prevVal);
}
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
// NOTE: populateMenus() expects {code, label} objects (like every
// other step's choices) — plain [code, label] pairs leave
// choice.code/choice.label undefined, so Chosen (desktop) ends up
// with unusable options.
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 ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
var opt = $("<option></option>")
.val(name)
.text(name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
if (!isMobile) tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (key, label) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", key)
.attr("placeholder", label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Apply Chosen to the new block's template selector on desktop.
if (!isMobile) newBlock.find("select.wiktku-dfn-tpl-select").chosen(chosenOptions);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: {},
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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));
});
// Notify Chosen (desktop) / the search widget (mobile) that the
// option list has changed.
sel.trigger("chosen:updated");
},
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", "Nimûne: 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("chosen:updated");
ta.focus();
});
if (isMobile) {
wrap.append(wrapNativeSelect(sel));
} else {
wrap.append(sel);
}
wrap.append($("<br>")).append(ta);
stepDiv.append(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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — uses the same code/name-priority
// autocomplete widget as mobile on both platforms (jquery.chosen
// can't rank code matches like "ang" above unrelated label
// substring matches — see wrapNativeSelect's showResults()).
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow wiktku-plain-select" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
row.append(wrapNativeSelect(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);
}
// 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("chosen:updated");
});
},
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);
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: "Eger ev kelîme di zaravayên din de bi awayekî cuda tê gotin an nivîsandin, wan lê zêde bike",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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 wiktku-plain-select" }));
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys, "Zarava hilbijêre");
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);
}
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);
});
});
},
/** 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);
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 "";
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) 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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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: "Wergera 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 — uses the same code/name-priority
// autocomplete widget as mobile on both platforms (jquery.chosen
// can't rank code matches like "ang" above unrelated label
// substring matches — see wrapNativeSelect's showResults()).
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow wiktku-plain-select" }));
langSel.attr("data-placeholder", "Zimanekî lê 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));
row.append(wrapNativeSelect(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);
}
// 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("chosen:updated");
});
},
// 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);
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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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 ên (ne jêgirtinan) 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));
});
// Notify Chosen (desktop) / the search widget (mobile) that the
// option list has changed.
sel.trigger("chosen:updated");
},
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 jêrê 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>");
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.");
});
}
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "\n* {{" + name + "}}");
$(this).val("");
sel.trigger("chosen:updated");
ta.focus();
});
if (isMobile) {
wrap.append(wrapNativeSelect(sel));
} else {
wrap.append(sel);
// build() is called again for every new beş (see the
// "+ Beş zêde bike" handler), after the initial page-wide
// .chosen() pass has already run — so this select needs
// its own chosen() call or it stays a plain, unsearchable
// native <select> on desktop.
sel.chosen(chosenOptions);
}
wrap.append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify chosen widgets of the programmatic value change.
if (!isMobile) fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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);
if (isMobile) {
populateMenus(input, step.keys, getDisabledKeys(step));
// On mobile return the wrapper; the select is inside it.
// Change events still fire on the inner select.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) nextKeyInput.chosen(chosenOptions);
}
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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
if (isMobile || step.forceCustomSearch) {
// forceCustomSearch (desktop only): use the same code/name
// autocomplete widget as mobile instead of jquery.chosen,
// which has no way to rank code matches (e.g. "ang") above
// unrelated label substring matches. Mark it plain-select
// so the bulk .chosen() pass below skips the now-hidden
// underlying <select>.
if (step.forceCustomSearch && !isMobile) input.addClass("wiktku-plain-select");
stepDiv.append(wrapNativeSelect(input));
} else {
stepDiv.append(input);
}
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
if (!isMobile) stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
if (!isMobile) $("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke"
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
if (!isMobile) startForm.find("select").not(".wiktku-plain-select").chosen(chosenOptions);
refresh();
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Pinned choices come from MediaWiki:Gadget-start.js/data.json (langChoices),
// already loaded into steps.lang.choices before build() runs.
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice.code; });
zimanJsonPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = { code: code, label: name, sortKey: sortKey };
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey.
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
var langMenu = $("#wiktku-start-lang-field");
populateMenus(langMenu, steps.lang.choices);
langMenu.trigger("chosen:updated");
var transMenus = $("#wiktku-start-trans .wiktku-start-step-keyfield");
populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
transMenus.trigger("chosen:updated");
var dundeMenus = $("#wiktku-start-dunde .wiktku-start-step-keyfield");
populateMenus(dundeMenus, steps.dunde.keys, getDisabledKeys(steps.dunde));
dundeMenus.trigger("chosen:updated");
var zaravaMenus = $("#wiktku-start-zarava .wiktku-start-step-keyfield");
populateMenus(zaravaMenus, steps.zarava.keys, getDisabledKeys(steps.zarava), "Zarava hilbijêre");
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page (if the "Li
// cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
});
}
// jquery.chosen/oojs-ui-core load in parallel with metaApiPromise above
// (mediawiki.api + the two API requests) — this block only needs to wait
// on the UI modules itself.
mw.loader.using(["jquery.chosen", "oojs-ui-core"], function () {
// Load metadata, langChoices, posChoices and tplDefs from the companion
// data page so they can be updated without touching this script.
// (Request was already fired via metaApiPromise above, in parallel
// with this module load — we're just picking up the result here.)
metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Populate lang choices (pinned list used by build() → ziman.json loader).
// data.json stores these as a plain {code: label} object.
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// 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.dfn.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 (now populated) lang choices.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
}).always(build);
});
});
//</nowiki>
0pvsjsionmk5eyredbbcgvu44wkigal
6291627
6291611
2026-07-27T07:26:00Z
Wikihez
7344
6291627
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
var chosenOptions = {
width: "20em",
disable_search_threshold: 0,
placeholder_text_single: "Yekî hilbijêre",
placeholder_text_multiple: "Hilbijêre",
no_results_text: "Nehat dîtin",
enable_split_word_search: true,
search_contains: true };
// ---- Searchable-picker configuration -----------------------------------
// jquery.chosen renders its search results in plain option order and gives
// no hook for re-ranking them, so it can never put an exact code match
// ("ang" -> "Îngilîziya kevn") above the substring matches. Rather than
// keep two pickers with two different behaviours, every platform now uses
// the widget below (wrapNativeSelect), which does its own ranking.
//
// Flip useNativePicker to false to hand desktop back to Chosen.
var useNativePicker = true;
var useChosen = !useNativePicker && !isMobile;
// ---- Diacritic-insensitive search --------------------------------------
// Lets "ingilizi" find "Îngilîzî", "kurdi" find "Kurdî", "sopani"
// find "Şopanî". NFD decomposition handles every letter written as
// base + combining accent (î ê û ç ş á é ö ...);
// FOLD_EXTRA covers the handful Unicode cannot decompose that way.
var FOLD_EXTRA = {
"ı": "i", // dotless i
"ø": "o", // o with stroke
"đ": "d", // d with stroke
"ð": "d", // eth
"ə": "e", // schwa
"ħ": "h", // h with stroke
"ł": "l", // l with stroke
"ŋ": "n", // eng
"ʉ": "u", // u bar
"ɨ": "i", // i bar
"ʒ": "z", // ezh
"ɛ": "e", // open e
"ɔ": "o", // open 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;
});
}
// Baseline picker styling. Only the highlight rule is injected on mobile --
// the rest is desktop-only so it cannot override whatever
// [[MediaWiki:Gadget-start.css]] already sets for the mobile layout.
// Once that CSS page covers desktop too, this block can be deleted.
(function injectPickerCSS() {
if (!useNativePicker) return;
if (document.getElementById("wiktku-ac-style")) return;
var css = ".wiktku-ac-results li.wiktku-ac-active { background: #eaf3ff; }\n";
if (!isMobile) {
css +=
".wiktku-native-select-wrap { position: relative; display: inline-block; vertical-align: top; }\n" +
".wiktku-ac-input { box-sizing: border-box; width: 20em; max-width: 100%; padding: 4px 6px;" +
" border: 1px solid #a2a9b1; border-radius: 2px; }\n" +
".wiktku-ac-input:focus { border-color: #36c; box-shadow: inset 0 0 0 1px #36c; outline: 0; }\n" +
".wiktku-ac-results { position: absolute; left: 0; z-index: 100; margin: 0; padding: 0;" +
" list-style: none; width: 20em; max-width: 100%; overflow-y: auto; background: #fff;" +
" border: 1px solid #a2a9b1; border-radius: 2px; box-shadow: 0 2px 4px rgba(0,0,0,.2); }\n" +
".wiktku-ac-results li { margin: 0; padding: 5px 8px; cursor: pointer; white-space: nowrap;" +
" overflow: hidden; text-overflow: ellipsis; }\n" +
".wiktku-ac-results li:hover { background: #eaf3ff; }\n";
}
var style = document.createElement("style");
style.id = "wiktku-ac-style";
style.appendChild(document.createTextNode(css));
document.head.appendChild(style);
}());
/**
* Replaces a native <select> with a searchable text input + ranked result
* list. Returns a wrapper element containing the original (hidden) select,
* so every call-site that reads .val() or listens for "change" on the
* select keeps working unchanged.
*
* After changing the option list, trigger "chosen:updated" on the select to
* re-cache -- the same event Chosen used, so existing call-sites still work.
*/
function wrapNativeSelect(select) {
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
// Multi-selects aren't supported by this widget, and a native multiple
// <select> is perfectly usable as-is -- leave it alone.
if (select.prop("multiple")) {
return wrapper.append(select);
}
var placeholder = select.attr("data-placeholder") || "Le bigere...";
// Hide the native select -- we drive it programmatically.
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();
// Full option cache -- rebuilt when chosen:updated fires.
var allOptions = [];
// Options currently rendered, in display order, plus the keyboard cursor.
var shown = [];
var activeIndex = -1;
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
var text = $(this).text();
// Language options read "Name (code)" -- score the name on its own
// 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"),
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. This is the whole point of the widget: a
// code that *is* the query, or begins with it, always outranks a name
// that merely contains it -- so "ang" lists "Îngilîziya kevn (ang)"
// first instead of burying it among every name containing "ang".
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 });
});
// Multi-word fallback (Chosen's enable_split_word_search): keep
// anything whose label contains every token, e.g. "kevn ing".
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>")
.text(opt.text)
.attr("data-value", opt.value)
.attr("aria-selected", "false");
// 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 () {
setActive(idx);
});
resultsList.append(li);
});
positionList();
resultsList.show();
textInput.attr("aria-expanded", "true");
}
function setActive(idx) {
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");
// Keep the cursor visible inside the scrollable list.
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 -- the behaviour Chosen had on desktop.
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);
});
// chosen:updated -> re-cache (fired after ziman.json loads, after options
// are enabled/disabled, and after programmatic .val() changes).
select.on("chosen:updated", function () {
cacheOptions();
syncInputToSelect();
if (resultsList.is(":visible")) showResults(textInput.val());
});
// No-op .chosen() so any stray desktop call-site doesn't need guarding.
select.chosen = function () { return select; };
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
/**
* Turns every plain <select> under `root` into a searchable picker -- Chosen
* when useChosen, otherwise the unified widget above. Selects marked
* .wiktku-plain-select opt out, and the call is idempotent, so it is safe to
* re-run after new rows or sense blocks are inserted.
*/
function initSelects(root) {
var targets = $(root).find("select").addBack("select").not(".wiktku-plain-select");
if (useChosen) {
targets.chosen(chosenOptions);
return;
}
targets.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));
});
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a plain {code: label} object (the format used for langChoices
// and posChoices in data.json) into the ordered array of choice objects
// ({code, label[, sortKey]}) that the rest of this script works with.
// isLang: true adds a sortKey (defaults to label) and tells populateMenus
// to show "(code)" after the label — used for 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// 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" : ""));
}
});
// Notify the picker (or Chosen) that the option list changed.
sel.trigger("chosen:updated");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
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.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
// Clear the picker's search box too, so it goes back to its
// placeholder instead of keeping the inserted template name.
sel.trigger("chosen:updated");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field wiktku-plain-select",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [{ code: code, label: genderLabel(code) }];
});
populateMenus(select, choices, null, "Cinsiyetê hilbijêre");
if ($.grep(choices, function (c) { return c.code === prevVal; }).length) {
select.val(prevVal);
}
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
// NOTE: populateMenus() expects {code, label} objects (like every
// other step's choices) — plain [code, label] pairs leave
// choice.code/choice.label undefined, so Chosen (desktop) ends up
// with unusable options.
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 ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
var opt = $("<option></option>")
.val(name)
.text(name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
tplSelect.trigger("chosen:updated");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (key, label) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", key)
.attr("placeholder", label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Give the new block's template selector the searchable picker.
initSelects(newBlock);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: {},
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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));
});
// Notify Chosen (desktop) / the search widget (mobile) that the
// option list has changed.
sel.trigger("chosen:updated");
},
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", "Nimûne: 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("chosen:updated");
ta.focus();
});
wrap.append(sel);
wrap.append($("<br>")).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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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("chosen:updated");
});
},
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: "Eger ev kelîme di zaravayên din de bi awayekî cuda tê gotin an nivîsandin, wan lê zêde bike",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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", "Zaravayekî lê bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys, "Zarava hilbijêre");
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("chosen:updated");
});
},
/** 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 "";
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) 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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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: "Wergera 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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("chosen:updated");
});
},
// 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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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 ên (ne jêgirtinan) 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));
});
// Notify Chosen (desktop) / the search widget (mobile) that the
// option list has changed.
sel.trigger("chosen:updated");
},
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 jêrê 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], "\n* {{" + name + "}}");
$(this).val("");
sel.trigger("chosen:updated");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beş zêde bike"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
// Populate only now: refreshTplSelect() looks the select up by id
// under #wiktku-start-cavkani, so running it before the wrap was
// appended found nothing and bailed out — which left the dropdown
// empty on every beş after the first, once the category members
// were already cached.
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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);
}
// Notify the picker widgets of the programmatic value change.
fields.filter("select").trigger("chosen:updated");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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);
if (!useChosen) {
populateMenus(input, step.keys, getDisabledKeys(step));
// Chosen got its disabled-key sync from the handler below, which
// relies on Chosen's own change event payload. Plain change events
// carry no payload, so recompute the taken set from the DOM instead.
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("chosen:updated");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
input.on("chosen:showing_dropdown", function () {
$(this).data("wiktku-defaultValue", $(this).val());
});
input.change(function (_, context) {
var inputs = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").not(this);
var oldValue = $(this).data("wiktku-defaultValue");
var newValue = context.selected;
inputs.find("option[value='" + oldValue + "']").prop('disabled', false);
inputs.find("option[value='" + newValue + "']").prop('disabled', true);
inputs.trigger("chosen:updated");
});
populateMenus(input, step.keys, getDisabledKeys(step));
return 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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("chosen:updated");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("chosen:updated");
}
// 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke"
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
// 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 (or Chosen, if useNativePicker is turned off).
initSelects(startForm);
refresh();
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Pinned choices come from MediaWiki:Gadget-start.js/data.json (langChoices),
// already loaded into steps.lang.choices before build() runs.
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice.code; });
zimanJsonPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = { code: code, label: name, sortKey: sortKey };
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey.
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
// Chosen has to be torn down and rebuilt when the option list is
// replaced; the native picker just needs the chosen:updated event.
var langMenu = $("#wiktku-start-lang-field");
populateMenus(langMenu, steps.lang.choices);
if (useChosen) langMenu.chosen("destroy").chosen(chosenOptions);
langMenu.trigger("chosen:updated");
var transMenus = $("#wiktku-start-trans .wiktku-start-step-keyfield");
populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
if (useChosen) transMenus.chosen("destroy").chosen(chosenOptions);
transMenus.trigger("chosen:updated");
var dundeMenus = $("#wiktku-start-dunde .wiktku-start-step-keyfield");
populateMenus(dundeMenus, steps.dunde.keys, getDisabledKeys(steps.dunde));
if (useChosen) dundeMenus.chosen("destroy").chosen(chosenOptions);
dundeMenus.trigger("chosen:updated");
var zaravaMenus = $("#wiktku-start-zarava .wiktku-start-step-keyfield");
populateMenus(zaravaMenus, steps.zarava.keys, getDisabledKeys(steps.zarava), "Zarava hilbijêre");
if (useChosen) zaravaMenus.chosen("destroy").chosen(chosenOptions);
// Was missing entirely: without this the Zarava picker kept serving the
// option list it cached before ziman.json replaced it.
zaravaMenus.trigger("chosen:updated");
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page (if the "Li
// cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
});
}
// The UI modules load in parallel with metaApiPromise above (mediawiki.api
// + the two API requests) — this block only needs to wait on them.
// jquery.chosen is only pulled in if it is actually going to be used.
var uiModules = ["oojs-ui-core"];
if (useChosen) uiModules.push("jquery.chosen");
mw.loader.using(uiModules, function () {
// Load metadata, langChoices, posChoices and tplDefs from the companion
// data page so they can be updated without touching this script.
// (Request was already fired via metaApiPromise above, in parallel
// with this module load — we're just picking up the result here.)
metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Populate lang choices (pinned list used by build() → ziman.json loader).
// data.json stores these as a plain {code: label} object.
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// 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.dfn.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 (now populated) lang choices.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
}).always(build);
});
});
//</nowiki>
81cyi8kck4zfrg244eammdttp1cgo0d
6291628
6291627
2026-07-27T09:35:14Z
Wikihez
7344
6291628
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage)
//<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 === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var action = mw.config.get("wgAction");
var pageExists = !!mw.config.get("wgArticleId");
var addLangMode = pageExists && mw.util.getParamValue("wiktkuaddlang") === "1";
if (action === "view" && pageExists) {
var targetContainer;
var skin = mw.config.get('skin');
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",
"Ji vê rûpelê re ziman an jî beşeke nû zêde bike");
return;
}
if (!(action === "edit" || action === "submit")) {
return;
}
if (!addLangMode && !$(".mw-newarticletext, .mw-newarticletextanon").length) {
return;
}
mw.notify(
"Amûr dest pê dike, hinek bisekinin.\nNot: jkx Sentaks (Syntax highlighting) neaktîv bikin",
{ type: "info", tag: "wiktku-start-notice", autoHide: true, autoHideSeconds: 10 }
);
// Kick off both metadata API requests as early as possible, needing only
// the mediawiki.api module rather than waiting for jquery.chosen and
// oojs-ui-core too (those are only needed later, to actually build the
// form). mediawiki.api is used all over the wiki, so this resolves
// near-instantly in practice — but we still wait for it properly rather
// than assuming mw.Api is already defined. The jquery.chosen/oojs-ui-core
// load happens in parallel with this, further down.
var dataJsonPromise, zimanJsonPromise;
var metaApiPromise = mw.loader.using("mediawiki.api").then(function () {
var metaApi = new mw.Api();
dataJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-start.js/data.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
zimanJsonPromise = metaApi.get({
action: "query",
prop: "revisions",
titles: "MediaWiki:Gadget-translation_editor.js/ziman.json",
rvprop: "content",
rvslots: "main",
formatversion: 2
});
return dataJsonPromise;
});
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, "\\$&");
}
// Regex source for a "== {{ziman|CODE}} ==" header, tolerant of extra
// whitespace. codePattern: escaped code for one language, or "[^}]+" for any.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Finds a language's header in wikitext (see langHeaderSource). Returns
// { index, match } or null.
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
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;
}
// Members of [[Kategorî:Çavkanî]] (source/citation templates), used by the
// "Ji Kategorî:Çavkanî şablonekê hilbijêre" jêder-source option. Fetched
// once on first use and cached — every citation row's picker shares it.
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");
// Draft cache for this page — lets the editor manually save/restore the
// whole wizard state (shared lang/pron + every section) via
// [[MediaWiki:Gadget-StorageUtils.js]]'s ObjectStorage, independent of
// any single browser session. Keyed per page so different articles'
// drafts don't collide.
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
// Mobile detection: MobileFrontend sets wgMFMode, or fall back to touch check.
var isMobile = !!mw.config.get("wgMFMode") || window.matchMedia("(pointer: coarse)").matches;
// ---- Searchable picker --------------------------------------------------
// Every dropdown in this form -- desktop and mobile alike -- uses the
// wrapNativeSelect() widget further down. jquery.chosen used to fill this
// role on desktop, but it renders search results in plain option order and
// offers no hook for re-ranking them, so it could never put an exact code
// match ("ang" -> "Îngilîziya kevn") above the substring matches. It is no
// longer loaded or referenced anywhere.
// ---- Diacritic-insensitive search --------------------------------------
// Lets "ingilizi" find "Îngilîzî", "kurdi" find "Kurdî", "sopani"
// find "Şopanî". NFD decomposition handles every letter written as
// base + combining accent (î ê û ç ş á é ö ...);
// FOLD_EXTRA covers the handful Unicode cannot decompose that way.
var FOLD_EXTRA = {
"ı": "i", // dotless i
"ø": "o", // o with stroke
"đ": "d", // d with stroke
"ð": "d", // eth
"ə": "e", // schwa
"ħ": "h", // h with stroke
"ł": "l", // l with stroke
"ŋ": "n", // eng
"ʉ": "u", // u bar
"ɨ": "i", // i bar
"ʒ": "z", // ezh
"ɛ": "e", // open e
"ɔ": "o", // open 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;
});
}
/**
* Replaces a native <select> with a searchable text input + ranked result
* list. Returns a wrapper element containing the original (hidden) select,
* so every call-site that reads .val() or listens for "change" on the
* select keeps working unchanged.
*
* After changing the option list, trigger "wiktku:refresh" on the select so
* the widget re-reads its <option>s.
*/
function wrapNativeSelect(select) {
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
// Multi-selects aren't supported by this widget, and a native multiple
// <select> is perfectly usable as-is -- leave it alone.
if (select.prop("multiple")) {
return wrapper.append(select);
}
var placeholder = select.attr("data-placeholder") || "Le bigere...";
// Hide the native select -- we drive it programmatically.
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();
// Full option cache -- rebuilt when wiktku:refresh fires.
var allOptions = [];
// Options currently rendered, in display order, plus the keyboard cursor.
var shown = [];
var activeIndex = -1;
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
if (!v) return; // skip the empty placeholder option
var text = $(this).text();
// Language options read "Name (code)" -- score the name on its own
// 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"),
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. This is the whole point of the widget: a
// code that *is* the query, or begins with it, always outranks a name
// that merely contains it -- so "ang" lists "Îngilîziya kevn (ang)"
// first instead of burying it among every name containing "ang".
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 });
});
// Multi-word fallback (Chosen's enable_split_word_search): keep
// anything whose label contains every token, e.g. "kevn ing".
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>")
.text(opt.text)
.attr("data-value", opt.value)
.attr("aria-selected", "false");
// 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 () {
setActive(idx);
});
resultsList.append(li);
});
positionList();
resultsList.show();
textInput.attr("aria-expanded", "true");
}
function setActive(idx) {
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");
// Keep the cursor visible inside the scrollable list.
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 -- the behaviour Chosen had on desktop.
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);
});
// wiktku:refresh -> re-cache the option list. Fired after ziman.json
// loads, after options are enabled/disabled, and after programmatic
// .val() changes. "chosen:updated" is kept as a legacy alias in case
// another gadget still fires the old Chosen event at these selects.
select.on("wiktku:refresh chosen:updated", 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. Selects
* marked .wiktku-plain-select opt out, and the call is idempotent, so it is
* safe to re-run after new rows or sense blocks are inserted.
*/
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));
});
}
var langsByCode = {};
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
// Zaravayên kurdî yên ku di beşa "Bi zaravayên din" de tên pêşkêşkirin —
// ev kod ji beşa Werger têne derxistin daku heman zimên du caran neyê xwendin.
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a plain {code: label} object (the format used for langChoices
// and posChoices in data.json) into the ordered array of choice objects
// ({code, label[, sortKey]}) that the rest of this script works with.
// isLang: true adds a sortKey (defaults to label) and tells populateMenus
// to show "(code)" after the label — used for 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";
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense, since a
// template-based sense has no "own" gloss for alternate forms, synonyms,
// antonyms, coordinate terms, hypernyms or hyponyms to attach to. Etîket,
// Nimûne and Jêgirtin stay visible either way.
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;
}
// 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) 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;
}
// 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#* {{bênimûne|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = text.replace(
new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "ig"),
"'''$1'''");
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + n.tr;
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + 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 < 200 && 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;
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) cite += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) cite += "|tercime=" + jeder.quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + jeder.quote;
if (jeder.quoteTr) out += "|tr=" + jeder.quoteTr;
if (jeder.quoteT) out += "|tercime=" + jeder.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
}
/**
* 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);
}
function makeWordListStep(id, name, question_fmt, header, templateName) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
if (words.length === 1) {
// Single word — keep the simple inline template.
return section +
"* {{" + templateName + "|" + lang + "|" +
words[0] + "}}\n";
}
// Multiple words — use {{kol3}} with one positional param per word.
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?',
question_br: true,
// 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" : ""));
}
});
// 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>");
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.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, "?");
}
insertAtCursor(ta[0], snippet);
$(this).val("");
// Clear the picker's search box too, so it goes back to its
// placeholder instead of keeping the inserted template name.
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel).append($("<br>")).append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
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.charAt(0) === "*" ? line : "* " + line;
}).join("\n");
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;
},
question_br: true,
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices).
choices: [],
output: function (values) {
var pos = values.pos || "";
var cinsiyet = values.cinsiyet || ""; // from the cinsiyet step
var lekTur = values.lektur || ""; // from the lêker transitivity step
var capitalizedPos = capitalize(pos);
var outputString = "\n=== " + capitalizedPos + " ===\n";
// Add ku-tewîn-nav for single-word navdêr/serenav with known gender.
if (!pageName.includes(" ") && cinsiyet && GENDER_BASE_LABELS[cinsiyet] &&
(pos === "navdêr" || pos === "serenav") && values.lang === "ku") {
outputString += "{{ku-tewîn-nav|" + pageName + "|" + GENDER_BASE_LABELS[cinsiyet];
if (pos === "serenav") outputString += "|y";
outputString += "}}\n";
}
outputString += "{{" + pos + "|" + values.lang;
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
outputString += "|c=" + cinsiyet;
} else if (values.lang === "ku") {
// Unknown gender — prompt editor to fill it in.
outputString += "|c=?";
}
} else if (/^lêker/.test(pos) && lekTur) {
outputString += "|c=" + lekTur;
}
if (values.tr) {
outputString += "|tr=" + values.tr;
}
outputString += "}}\n";
return outputString;
}
},
// 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?',
question_br: true,
visible: function (values) {
var meta = metadata[values.lang];
return !!(values.lang && (values.pos === "navdêr" || values.pos === "serenav") &&
meta && meta.g && meta.g.length);
},
build: function (stepDiv, values) {
var select = stepDiv.find("select.wiktku-start-step-field");
if (!select.length) {
select = $(mw.html.element("select", {
"class": "wiktku-start-step-field wiktku-plain-select",
id: "wiktku-start-cinsiyet-field" }));
stepDiv.append(select);
}
if (!values) return select;
var meta = metadata[values.lang];
var codes = (meta && meta.g) || [];
var prevVal = select.val();
var choices = $.map(codes, function (code) {
return [{ code: code, label: genderLabel(code) }];
});
populateMenus(select, choices, null, "Cinsiyetê hilbijêre");
if ($.grep(choices, function (c) { return c.code === prevVal; }).length) {
select.val(prevVal);
}
return select;
},
output: function () { return ""; } // consumed by pos.output
},
// Lêker transitivity — shown for any lêker variant.
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
question_br: true,
visible: function (values) {
return values.lang && values.pos && /^lêker/.test(values.pos);
},
// Dictionary: value → display label. Add new types here.
// NOTE: populateMenus() expects {code, label} objects (like every
// other step's choices) — plain [code, label] pairs leave
// choice.code/choice.label undefined, so Chosen (desktop) ends up
// with unusable options.
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 ""; } // consumed by pos.output
},
tr: {
id: "tr",
name: "transkrîpsiyon",
question_fmt: "Transkrîpsiyona kelîmeyê",
multiple: false,
narrow: true,
visible: function (values) {
return values.lang && values.pos && needsTransliteration(values.lang);
} },
dfn: {
id: "dfn",
// required: true, ger mane tinebe [[Şablon:mane?]] bi kar bîne
name: "Mane",
question_fmt: "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin! Ji bo tercimeyan beşa 'Werger' bi kar bîne!",
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
example: function (values) {
// posExamples loaded from [[MediaWiki:Gadget-start.js/data.json]].
var examples = steps.dfn.posExamples || {};
var full = values.pos || "";
return examples[full] || "";
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function (stepDiv) {
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", "Mane…");
dfnArea.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// — 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 — used by updateTplOptions
// to remove/re-add options (the only approach compatible with Chosen.js).
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
var opt = $("<option></option>")
.val(name)
.text(name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
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.
// This is the only approach that works correctly with both native <select>
// and Chosen.js on desktop.
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;
}
});
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
dfnArea.show();
tplTitleInput.hide().val("");
}
}
// 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ûpelê…")
.hide();
// Grow the sense list when the title field of the last block gets content,
// mirroring what onDfnInput does for the plain mane textarea.
tplTitleInput.on("input", function () {
steps.dfn.onDfnInput(stepDiv, block, this);
});
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either way.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
}
// Store on the block so restoreDfn() can re-sync visibility
// after programmatically setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
tplTitleInput.show().focus();
} else {
dfnArea.show();
tplTitleInput.hide().val("");
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput);
block.append(tplRow);
block.append(dfnArea);
// Apply lang filter on initial build.
updateTplOptions();
// — Sub-field groups —
// All groups are always visible with a placeholder textarea.
// Nimûne is last in the DOM and last in the output.
var allGroups = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", rows: 1, narrow: true, izah: "xisûsen, Mêrdîn, anatomî, Badînî" },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", rows: 1, narrow: true, izah: "wekî etîket lê xuya nabe tenê kategorîze dike" },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê" },
{ 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-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" },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e" },
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false }
];
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
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 () {
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", "Transkrîpsiyona 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 kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": ");
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
var lbl = $("<span></span>").addClass("wiktku-dfn-sublabel").text(g.label + ": " + g.izah);
var list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
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", g.label + "…");
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow);
});
list.append($("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder (citation) 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").text("Jêgirtin: ");
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");
var dragHandle = $("<span></span>")
.addClass("wiktku-jeder-drag-handle")
.attr("title", "Bişkûne û bikişîne da ku rêzê biguhezîne");
// 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 () {
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(dragHandle).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", "Transkrîpsiyona 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 = $("<input type='text'>")
.addClass("wiktku-dfn-quote-citoid-input wiktku-start-narrow")
.attr("placeholder", "URL, DOI an ISBN binivîse ku otomatîk were tijekirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi Citoid bîne",
classes: [ "wiktku-dfn-quote-citoid-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.citoidHelper.ensure().then(function () {
return 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>"));
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim"));
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();
if (!chosen) 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 to
// "Bi destê xwe binivîsim" mode with {{Şablon}} pre-filled,
// so the saved value is always exactly what's in that
// (editable) textarea — no separate hidden field that can
// end up empty and silently fall back to "çavkanî?".
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", "Navê şablonê lê 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…");
function showCatResults(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.).
function selectCatTpl(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;
$.each(def.fields, function (key, label) {
var row = $("<div></div>").addClass("wiktku-dfn-quote-src-row");
var fieldLbl = $("<label></label>").text(label + ": ");
var inp = $("<textarea></textarea>")
.attr("rows", 2)
.addClass("wiktku-dfn-quote-src-input wiktku-start-narrow")
.attr("data-field-key", key)
.attr("placeholder", label + "…");
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
// Make citation rows sortable (drag to reorder).
// The last row is always the empty trailing row — cancel its drag.
mw.loader.using("jquery.ui.sortable", function () {
jederList.sortable({
handle: ".wiktku-jeder-drag-handle",
axis: "y",
items: "li:not(:last-child)",
tolerance: "pointer"
});
});
jederGroupDiv.append(jederLbl).append(jederList);
block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
return block;
},
/** Grows the sense list when the last mane textarea gets content. */
onDfnInput: function (stepDiv, block, textarea) {
var val = $.trim($(textarea).val());
var list = stepDiv.find("ol.wiktku-dfn-list");
var isLast = block.is(list.children("li:last-child"));
if (val && isLast) {
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
// Give the new block's template selector the searchable picker.
initSelects(newBlock);
}
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) {
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);
});
list.append($("<li></li>").append(next));
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, posExamples, jederDefs).
tplDefs: {},
posExamples: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
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;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readList("wiktku-dfn-etiket"),
kategori: readList("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")
});
});
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);
});
// True only when at least one sense has a regular mane (needs linkify).
var hasRegularMane = senses.some(function (s) {
return !s.tpl && s.mane !== "";
});
// nimûne/jêder must never pass through linkify, only the mane text.
// defLines (linkified) and citeLines (kept aside) stay parallel;
// a {{wiktku-cite-N}} token in defLines marks where to splice back.
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
$.each(senses, function (_, sense) {
// — 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 tplLabelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
var idx = defLines.length;
var cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
if (dfn) {
dfn = stripWikilinks(dfn);
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
} else {
dfn = "{{mane?|" + lang + "}}";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var labelTpl = buildSenseTpl(sense, "etiket", lang, "e");
var out = "# " + (katTpl ? katTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
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);
var cite = buildCite(sense, lang);
var idx = defLines.length;
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
});
// 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)] || "";
});
}
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with bênimûne.
text = "# {{mane?|" + lang + "}}\n#* {{bênimûne|" + lang + "}}\n";
callback(text);
return;
}
if (!hasRegularMane) {
// All filled senses are template senses — no linkify needed.
callback(restoreCites(text));
return;
}
var didTryToLoad = function () {
if (typeof $.wiktviLinkify === "function") {
$.wiktviLinkify(text, function (linked) {
callback(restoreCites(linked));
});
} else {
callback(restoreCites(text));
}
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse", "Jê", "g"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse", "Têkildar", "g"),
binereherwiha: makeWordListStep("binereherwiha", "binêre herwiha", "Kelîmeyên din ên eleqedar binivîse", "Binêre herwiha", "g"),
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));
});
// Notify Chosen (desktop) / the search widget (mobile) that the
// option list has 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", "Nimûne: 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($("<br>")).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,
// Only when a regular sense has content (real mane, or {{mane?}}
// with other fields filled in) — a şablon sense never counts.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (!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: {
"navz": 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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: "Eger ev kelîme di zaravayên din de bi awayekî cuda tê gotin an nivîsandin, wan lê zêde bike",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {
"navz": true
},
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", "Zaravayekî lê bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys, "Zarava hilbijêre");
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 "";
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) 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");
if (!wordLines) return undefined;
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
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: "Wergera 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 — chosen is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Zimanekî lê 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 "";
// 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 — nothing to output, and gloss.text is
// empty so there's no caption to give {{werger-ser}} anyway.
if (!gloss.text && !rows.length) 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 ên (ne jêgirtinan) 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));
});
// Notify Chosen (desktop) / the search widget (mobile) that the
// option list has 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 jêrê 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], "\n* {{" + name + "}}");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append($("<br>")).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beş zêde bike"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
// Populate only now: refreshTplSelect() looks the select up by id
// under #wiktku-start-cavkani, so running it before the wrap was
// appended found nothing and bailed out — which left the dropdown
// empty on every beş after the first, once the category members
// were already cached.
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);
var ordered_steps = [
steps.lang, steps.pron, steps.pos, steps.cinsiyet, steps.lektur, steps.tr,
steps.dfn, steps.je, steps.tekildar,
steps.etym, steps.dunde, steps.binereherwiha, steps.zarava, steps.trans, steps.cavkani];
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "cinsiyet", "lektur", "tr",
"dfn", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani"
];
// ── 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;
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 list so build() recreates it cleanly.
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();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set programmatically (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// 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
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
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);
});
subList.append($("<li></li>").append(ta));
}
});
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-etiket": sense.etiket,
"wiktku-dfn-kategori": sense.kategori,
"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);
});
// 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);
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);
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());
}
});
}
// 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;
}
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();
}
});
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 === "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");
});
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
steps.dfn.getOutput(values, function (output) {
entry += output;
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);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
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);
});
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang/pron header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var entry = steps.lang.output(sharedValues);
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// 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) { return s.mane; }).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;
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);
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = $("<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 didFocus = false;
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();
// 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", "Transkrîpsiyon…");
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ê 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();
});
}());
// 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");
li.find(".wiktku-dfn-nimune-quote").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", "Transkrîpsiyona 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");
li.find(".wiktku-dfn-nimune-quote").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ê ya 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) {
if (typeof(step.visible) === "function" && !step.visible(values)) {
$("#wiktku-start-" + stepId).slideUp();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
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);
}
var didSlideDown;
if (!didFocus && stepDiv.css("display") === "none" && step.required) {
didSlideDown = function (evt) {
input.focus();
};
didFocus = true;
}
stepDiv.slideDown(didSlideDown);
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
continueBtn.setDisabled(!canContinue);
}
function destroy() {
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard, #editform").slideDown();
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and the navz pseudo-code.
// 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 at all — just append.
var sep = existing && !/\n\s*$/.test(existing) ? "\n\n" : "";
return existing + sep + 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 separated by newlines.
var joined = sections.map(function (s) {
// Ensure each section ends with exactly one newline before the next.
return s.content.replace(/\n+$/, "");
}).join("\n");
return preamble + 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 di rûpelê de 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(" Naveroka rûpelê paqij bike (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>");
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
spinner.show();
getWikitext(function (wikitext) {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page and start fresh — just
// write the newly generated wikitext as the full content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// 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!");
$("#wiktku-preview-container").remove();
destroy();
});
});
$.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);
stepDiv.append($(mw.html.element("h3", {
"class": "wiktku-start-step-name" }, new mw.html.Raw(mw.html.element("label", {
"for": "wiktku-start-" + step.id + "-field" }, name)))));
if (step.question_fmt) {
stepDiv.append($(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, step.question_fmt)));
if (step.question_br) stepDiv.append($("<br>"));
}
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);
} 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.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append($("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Insert tab bar just before the first per-section step.
if (step.id === "pos") startForm.append(sectionTabsDiv);
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ş zêde bike",
disabled: true
});
addSectionBtn.on("click", function () {
saveCurrentSection();
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
var stepDiv = $("#wiktku-start-dfn");
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 === "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;
}
var stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
renderSectionTabs();
refresh();
$("html, body").animate({ scrollTop: sectionTabsDiv.offset().top - 20 }, 300);
});
/** 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();
return;
}
sectionTabsDiv.show();
$.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);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection) return;
saveCurrentSection();
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
});
}(i));
sectionTabsDiv.append(tab);
});
// Enable "Beş zêde bike" 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);
}
renderSectionTabs();
/**
* Restores a full cached draft: shared lang/pron fields, then every
* section (via the same restoreSection()/refresh()/renderSectionTabs()
* sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, 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);
}
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();
mw.notify("Danûstandina berê ya tomarkirî 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();
});
// 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 () {
mw.notify("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
// which is invisible on mobile and easy to miss on desktop.
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke"
});
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 (see the
// ziman.json .then() callback below), once the full language list
// is available.
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache tomar bike",
title: "Rewşa niha ya vê formê (ziman, bilêvkirin û hemû beş) li cache (localStorage) tomar 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();
var snapshot = {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
sections: sections,
activeSection: activeSection
};
// 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 {
formCache.Set(snapshot);
mw.notify("Rewşa formê li cache hate tomarkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Tomarkirina li cache serneket.", { type: "error", tag: "wiktku-cache-save" });
}
});
footer.append(saveCacheBtn.$element);
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Daneyên tomarkirî yên vê rûpelê ji cache jê bibe (formeke ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
mw.notify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
footer.append(clearCacheBtn.$element);
// ── Preview checkboxes ───────────────────────────────────────────────
// Wikitext preview (raw text panel)
var previewCheck = $("<input type='checkbox' id='wiktku-preview-toggle'>");
var previewLabel = $("<label for='wiktku-preview-toggle'></label>")
.text(" Pêşdîtina wîkîtekstê");
var previewPanel = $("<pre id='wiktku-preview-panel'></pre>").hide();
// Live rendered preview (parsed HTML panel, same logic as AjaxEdit).
// Not available on mobile — API polling causes too many problems on small screens.
var livePreviewCheck = null;
var livePreviewPanel = null;
if (!isMobile) {
livePreviewCheck = $("<input type='checkbox' id='wiktku-live-preview-toggle'>");
livePreviewPanel = $("<div id='wiktku-live-preview-panel'></div>").hide();
}
// Container that holds both panels and switches between stacked / side-by-side.
var previewContainer = $("<div id='wiktku-preview-container'></div>");
previewContainer.append(previewPanel);
if (!isMobile) previewContainer.append(livePreviewPanel);
var previewTimer = null;
var livePreviewIntervalId = -1;
var livePreviewActive = false; // guards in-flight fetches after uncheck
/** Rebuilds wikitext preview if its checkbox is checked. */
function schedulePreview() {
if (!previewCheck.prop("checked")) return;
clearTimeout(previewTimer);
// Debounce: wait 400 ms after the last keystroke before rebuilding.
previewTimer = setTimeout(function () {
getWikitext(function (wikitext) {
previewPanel.text(wikitext).show();
updatePanelLayout();
});
}, 400);
}
/** Fetches parsed HTML and populates the live preview panel. */
function loadLivePreview() {
getWikitext(function (wikitext) {
new mw.Api().parse(wikitext, { title: mw.config.get("wgPageName") }).done(function (html) {
// Discard result if the user unchecked while the fetch was in flight.
if (!livePreviewActive) return;
livePreviewPanel.html(html).show();
updatePanelLayout();
});
});
}
/** Adjusts panel widths depending on which previews are visible. */
function updatePanelLayout() {
var wikitextVisible = previewCheck.prop("checked");
var liveVisible = livePreviewCheck && livePreviewCheck.prop("checked");
if (wikitextVisible && liveVisible) {
// Side by side — each panel takes roughly half the available width.
previewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
livePreviewPanel.css({ "flex": "1 1 45%", "min-width": "280px" });
} else {
// Full width for whichever is visible — only touch panels that are
// actually shown so that setting flex doesn't override display:none.
if (wikitextVisible) previewPanel.css({ "flex": "1 1 100%", "min-width": "" });
if (liveVisible && livePreviewPanel) livePreviewPanel.css({ "flex": "1 1 100%", "min-width": "" });
}
}
previewCheck.on("change", function () {
if ($(this).prop("checked")) {
schedulePreview();
} else {
clearTimeout(previewTimer);
previewPanel.hide();
updatePanelLayout();
}
});
if (!isMobile) {
livePreviewCheck.on("change", function () {
if ($(this).prop("checked")) {
livePreviewActive = true;
livePreviewPanel.show();
loadLivePreview();
livePreviewIntervalId = setInterval(loadLivePreview, 500);
} else {
livePreviewActive = false;
clearInterval(livePreviewIntervalId);
livePreviewIntervalId = -1;
livePreviewPanel.hide().empty();
updatePanelLayout();
}
});
}
// Listen on all input/change events on the form and update both previews.
startForm.on("input change", function () {
schedulePreview();
});
footer.append($("<span style='margin-left:1em'></span>")
.append(previewCheck).append(previewLabel));
if (!isMobile) {
var livePreviewLabel = $("<label for='wiktku-live-preview-toggle'></label>")
.text(" Pêşdîtina zindî");
footer.append($("<span style='margin-left:1em'></span>")
.append(livePreviewCheck).append(livePreviewLabel));
}
startForm.append(footer);
$("#editform").hide().before(startForm);
// Insert preview container (holds both panels) after the form.
startForm.after(previewContainer);
// 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 (or Chosen, if useNativePicker is turned off).
initSelects(startForm);
refresh();
// Zimanan ji MediaWiki:Gadget-translation_editor.js/ziman.json bar bike.
// Kodên ku bi "-pro" diqedin û navên ku "zimanê destan" tê de heye
// tên derxistin. Zimanên xardkirî li serî dimînin, yên din
// bi alfabeyê li dû wan tên.
// Pinned choices come from MediaWiki:Gadget-start.js/data.json (langChoices),
// already loaded into steps.lang.choices before build() runs.
var pinnedChoices = steps.lang.choices.slice();
var pinnedCodes = $.map(pinnedChoices, function (choice) { return choice.code; });
zimanJsonPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var zimanData;
try { zimanData = JSON.parse(content); } catch (e) { zimanData = {}; }
var extraChoices = [];
$.each(zimanData, function (code, name) {
// Filter: skip proto-language codes and legendary/reconstructed langs.
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
// Skip codes already in the pinned list (they are handled above).
if (pinnedCodes.indexOf(code) !== -1) return;
// sortKey: bare name without leading "zimanê " for sorting.
var sortKey = name.replace(/^ziman[eê]\s+/i, "");
var choice = { code: code, label: name, sortKey: sortKey };
extraChoices.push(choice);
langsByCode[code] = choice;
});
// Populate langsByCode for pinned choices too.
$.each(pinnedChoices, function (i, choice) {
// Ensure pinned choices have a sortKey.
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
// Sort the extra choices alphabetically by sort key.
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
// Final list: pinned first, then the rest.
var allChoices = pinnedChoices.concat(extraChoices);
steps.lang.choices = allChoices;
steps.trans.keys = allChoices;
steps.dunde.keys = allChoices;
steps.zarava.keys = getZaravaKeys(allChoices);
// Replacing the option list only needs a wiktku:refresh so each
// picker re-reads its <option>s.
var langMenu = $("#wiktku-start-lang-field");
populateMenus(langMenu, steps.lang.choices);
langMenu.trigger("wiktku:refresh");
var transMenus = $("#wiktku-start-trans .wiktku-start-step-keyfield");
populateMenus(transMenus, steps.trans.keys, getDisabledKeys(steps.trans));
transMenus.trigger("wiktku:refresh");
var dundeMenus = $("#wiktku-start-dunde .wiktku-start-step-keyfield");
populateMenus(dundeMenus, steps.dunde.keys, getDisabledKeys(steps.dunde));
dundeMenus.trigger("wiktku:refresh");
var zaravaMenus = $("#wiktku-start-zarava .wiktku-start-step-keyfield");
populateMenus(zaravaMenus, steps.zarava.keys, getDisabledKeys(steps.zarava), "Zarava hilbijêre");
// Was missing entirely: without this the Zarava picker kept serving the
// option list it cached before ziman.json replaced it.
zaravaMenus.trigger("wiktku:refresh");
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page (if the "Li
// cache tomar bike" button was ever used here).
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
});
}
// oojs-ui-core loads in parallel with metaApiPromise above (mediawiki.api
// + the two API requests) — this block only needs to wait on it.
// jquery.chosen is deliberately not loaded: the form has one dropdown
// implementation now (wrapNativeSelect).
mw.loader.using(["oojs-ui-core"], function () {
// Load metadata, langChoices, posChoices and tplDefs from the companion
// data page so they can be updated without touching this script.
// (Request was already fired via metaApiPromise above, in parallel
// with this module load — we're just picking up the result here.)
metaApiPromise.then(function (data) {
var page = data.query.pages[0];
var content = page &&
page.revisions &&
page.revisions[0].slots.main.content;
var d;
try { d = JSON.parse(content); } catch (e) { d = {}; }
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Populate lang choices (pinned list used by build() → ziman.json loader).
// data.json stores these as a plain {code: label} object.
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// 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.dfn.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 (now populated) lang choices.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
}).always(build);
});
});
//</nowiki>
gtlcxilkwgtrsxpgbkny45qamuw3la2
superqehreman
0
1325422
6291560
6260487
2026-07-26T13:20:52Z
Wikihez
7344
/* Navdêr */
6291560
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{ku-tewîn-nav|superqehreman|mê û nêr}}
{{navdêr|ku|z=mn}}
# Di [[edebiyat]]a zarokan û [[fantastîk]] de her [[karakter]]ê [[xeyalî]], [[axlebî]] bi [[qewet]]eke [[dersirûştî]], ê ku li dijî [[sûc]]an [[şer kirin|şer dike]].
#* {{jêder-malper|ku|jêgirtin=Birçî bihêle, bêav bihêle, dîl bigre paşê wek '''super qehreman''' wî ji çalê derxe.|nivîskar=Çayan Okudici|sernav=Bizina reş, qurta qurta avê û qertel|malper=gazeteduvar.com|url=https://www.gazeteduvar.com.tr/bizina-res-qurta-qurta-ave-u-qertel-haber-1629016|sal=2023}}
#* {{jêder-malper|ku|jêgirtin="'''Super Qehremanê''' kurd Gênco" hat|nivîskar=Cemil Oguz|url=https://diyarname.com/news.php?Idx=25354|malper=diyarname.com|sal=2017|sernav='Super Qehremanê kurd Gênco' hat}}
#* {{jêder-kovar|ku|jêgirtin=Di pîranîya Cîhana Batman û '''super-qehremanan''' da mirovên normal ne girîng în lê dîsa ev rêze-xêzefîlm vê hişmendîyê xera dike û di cîhana mirovên normal da cih dide wan jî.|kovar=Temaşe|sernav=Xêzefîlmê Xêzefîlman Batman|nivîskar=Mehmûd Beyto|url=https://www.kovaratemase.com/wp-content/uploads/2021/03/Kovara-Temase-3.pdf|sal=2021}}
==== Formên alternatîv ====
* {{hw|ku|super-qehreman}}
==== Etîmolojî ====
{{yekser|en|ku|superhero}} ji {{qertaf|en|super|hero|w2=qehreman|nekat=1}}; wekheva {{qertaf|ku|super|qehreman}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Superheld|n}}
* {{Z|bg}}: {{W+|bg|супергеро́й|n}}
* {{Z|cs}}: {{W-|cs|superhrdina|n}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W+|cmn|超級英雄|tr=chāojí yīngxióng}}
* {{Z|da}}: {{W-|da|superhelt|n}}
* {{Z|eo}}: {{W-|eo|superheroo}}
* {{Z|et}}: {{W-|et|superkangelane}}
* {{Z|fi}}: {{W+|fi|supersankari}}
* {{Z|fr}}: {{W+|fr|super-héros|n}}, {{W+|fr|superhéros|n}}
* {{Z|gl}}: {{W-|gl|superheroe|n}}
* {{Z|ka}}: {{W-|ka|სუპერგმირი}}
* {{Z|hi}}: {{W-|hi|सुपर हीरो|n}}
* {{Z|nl}}: {{W+|nl|superheld|n}}
* {{Z|en}}: {{W+|en|superhero}}
* {{Z|ga}}: {{W-|ga|sárlaoch|n}}
* {{Z|it}}: {{W+|it|supereroe|n}}
* {{Z|is}}: {{W-|is|ofurhetja|m}}
* {{Z|ja}}: {{W+|ja|スーパーヒーロー|tr=sūpāhīrō|sc=Jpan}}
* {{Z|ca}}: {{W-|ca|superheroi|n}}
* {{Z|ko}}: {{W-|ko|슈퍼히어로}}
* {{Z|lv}}: {{W-|lv|supervaronis}}
* {{Z|lt}}: {{W-|lt|superherojus}}
* {{Z|lb}}: {{W-|lb|Superheld}}
* {{Z|mk}}: {{W-|mk|суперхе́рој|n}}
* {{Z|ms}}: {{W+|ms|adiwira}}
* {{Z|mt}}: {{W-|mt|supereroj|n}}
* {{Z|mr}}: {{W-|mr|सुपरहिरो|n}}
* {{Z|hu}}: {{W+|hu|szuperhős}}
* {{Z|mn}}: {{W-|mn|супер баатар}}
* {{Z|pl}}: {{W+|pl|superbohater|n}}
* {{Z|pt}}: {{W+|pt|super-herói}}
* {{Z|kk}}: {{W-|kk|суперқаһарман}}
* {{Z|ru}}: {{W+|ru|супергеро́й|n}}
* {{Z|es}}: {{W+|es|superhéroe}}
* {{Z|sw}}: {{W-|sw|shujaa wa ajabu|n}}, {{W-|sw|shujaa mkuu|n}}
* {{Z|sv}}: {{W+|sv|superhjälte|g}}
* {{Z|th}}: {{W-|th|ซูเปอร์ฮีโร่|tr=sūpexr̒ḥīrò}}, {{W-|th|ยอดมนุษย์}}
* {{Z|tr}}: {{W-|tr|süper kahraman}}
* {{Z|uk}}: {{W-|uk|супергеро́й|n}}
* {{Z|vi}}: {{W-|vi|siêu anh hùng}}
* {{Z|cy}}: {{W-|cy|siwpyr-arwr|n}}, {{W-|cy|siwpyr-arwres|m}}
* {{Z|el}}: {{W+|el|υπερήρωας|n}}
{{werger-bin}}
:{{werger çavkanî|en|superhero|beş=Ev qismê Wergerê|oldid=83357988}}
s30kvq53rlgka5k3fausa8jhcr1jcvs
Şablon:arşîvkirin
10
1357236
6291613
6235760
2026-07-26T22:44:29Z
Wikihez
7344
dark mode
6291613
wikitext
text/x-wiki
{| class="tablo-flip"
|-
|<div class="float-right-flip" style="font-size:85%; border:solid 1px var(--wikt-palette-lightgrey, #ccc); padding:0.2em; margin-right:0.5em; text-align:center; width:180px; color:var(--wikt-palette-black, #202122); background: linear-gradient(to bottom, var(--wikt-palette-paleblue, #f8f9fa) 0%, var(--wikt-palette-lightgrey, #c8d2dc) 100%);">
[[Wêne:Replacement_filing_cabinet.svg|50px|link=]]<br>
{{hlist|class=inline
|<div>
{{{1}}}
</div>
}}
|}<noinclude>{{belgekirin}}</noinclude>
evpoqc52e922tbkuovvna011a5i7s06
Modul:quote
828
1357519
6291566
6291540
2026-07-26T14:39:05Z
Wikihez
7344
6291566
Scribunto
text/plain
--[=[
This module contains functions to implement quote-* templates.
Author: Benwing2; conversion into Lua of {{quote-meta/source}} template,
written by Sgconlaw with some help from Erutuon and Benwing2.
The main interface is quote_t(). Note that the source display is handled by source(), which reads both the
arguments passed to it *and* the arguments passed to the parent template, with the former overriding the latter.
]=]
local export = {}
--[=[
FIXME:
1. Dates like `1 march 2025` with the day of the month being 1 and the month lowercased are treated as month-year-only.
The relevant code checks for a capitalized word; it should probably instead check for the actual month names or
abbrevs.
]=]
-- Named constants for all modules used, to make it easier to swap out sandbox versions.
local check_isxn_module = "Module:check isxn"
local debug_track_module = "Module:debug/track"
local en_utilities_module = "Module:en-utilities"
local italics_module = "Module:italics"
local labels_module = "Module:labels"
local languages_module = "Module:languages"
local languages_error_module = "Module:languages/error"
local links_module = "Module:links"
local number_utilities_module = "Module:number utilities"
local parameters_module = "Module:parameters"
local parse_utilities_module = "Module:parse utilities"
local qualifier_module = "Module:qualifier"
local roman_numerals_module = "Module:roman numerals"
local scribunto_module = "Module:Scribunto"
local script_utilities_module = "Module:script utilities"
local scripts_module = "Module:scripts"
local string_pattern_escape_module = "Module:string/patternEscape"
local string_replacement_escape_module = "Module:string/replacementEscape"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local template_parser_module = "Module:template parser"
local usex_module = "Module:usex/ceribandin"
local usex_templates_module = "Module:usex/templates"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"
local concat = table.concat
local insert = table.insert
local new_title = mw.title.new
local remove = table.remove
local require = require
local sort = table.sort
local u = mw.ustring.char
local ugsub = mw.ustring.gsub
local umatch = mw.ustring.match
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
-- Use HTML entities here to avoid parsing issues (esp. with brackets)
local SEMICOLON_SPACE = "; "
local SPACE_LBRAC = " ["
local RBRAC = "]"
local TEMP_LT = u(0xFFF1)
local TEMP_GT = u(0xFFF2)
local TEMP_LBRAC = u(0xFFF3)
local TEMP_RBRAC = u(0xFFF4)
local TEMP_SEMICOLON = u(0xFFF5)
local function apply_subst(...)
apply_subst = require(usex_module).apply_subst
return apply_subst(...)
end
local function check_isbn(...)
check_isbn = require(check_isxn_module).check_isbn
return check_isbn(...)
end
local function check_issn(...)
check_issn = require(check_isxn_module).check_issn
return check_issn(...)
end
local function debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
local function decode_entities(...)
decode_entities = require(string_utilities_module).decode_entities
return decode_entities(...)
end
local function embedded_language_links(...)
embedded_language_links = require(links_module).embedded_language_links
return embedded_language_links(...)
end
local function escape_wikicode(...)
escape_wikicode = require(parse_utilities_module).escape_wikicode
return escape_wikicode(...)
end
local function find_best_script_without_lang(...)
find_best_script_without_lang = require(scripts_module).findBestScriptWithoutLang
return find_best_script_without_lang(...)
end
local function find_parameters(...)
find_parameters = require(template_parser_module).find_parameters
return find_parameters(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function format_processed_labels(...)
format_processed_labels = require(labels_module).format_processed_labels
return format_processed_labels(...)
end
local function format_qualifier(...)
format_qualifier = require(qualifier_module).format_qualifier
return format_qualifier(...)
end
local function format_usex(...)
format_usex = require(usex_module).format_usex
return format_usex(...)
end
local function get_lang(...)
get_lang = require(languages_module).getByCode
return get_lang(...)
end
local function get_number(...)
get_number = require(number_utilities_module).get_number
return get_number(...)
end
local function get_script(...)
get_script = require(scripts_module).getByCode
return get_script(...)
end
local function gsplit(...)
gsplit = require(string_utilities_module).gsplit
return gsplit(...)
end
local function page_should_be_ignored(...)
page_should_be_ignored = require(usex_templates_module).page_should_be_ignored
return page_should_be_ignored(...)
end
local function parse_inline_modifiers(...)
parse_inline_modifiers = require(parse_utilities_module).parse_inline_modifiers
return parse_inline_modifiers(...)
end
local function parse_inline_modifiers_from_segments(...)
parse_inline_modifiers_from_segments = require(parse_utilities_module).parse_inline_modifiers_from_segments
return parse_inline_modifiers_from_segments(...)
end
local function parse_multi_delimiter_balanced_segment_run(...)
parse_multi_delimiter_balanced_segment_run = require(parse_utilities_module).parse_multi_delimiter_balanced_segment_run
return parse_multi_delimiter_balanced_segment_run(...)
end
local function parse_term_with_lang(...)
parse_term_with_lang = require(parse_utilities_module).parse_term_with_lang
return parse_term_with_lang(...)
end
local function pattern_escape(...)
pattern_escape = require(string_pattern_escape_module)
return pattern_escape(...)
end
local function pluralize(...)
pluralize = require(en_utilities_module).pluralize
return pluralize(...)
end
local function process_params(...)
process_params = require(parameters_module).process
return process_params(...)
end
local function remove_links(...)
remove_links = require(links_module).remove_links
return remove_links(...)
end
local function roman_to_arabic(...)
roman_to_arabic = require(roman_numerals_module).roman_to_arabic
return roman_to_arabic(...)
end
local function replacement_escape(...)
replacement_escape = require(string_replacement_escape_module)
return replacement_escape(...)
end
local function scribunto_parameter_key(...)
scribunto_parameter_key = require(scribunto_module).scribunto_parameter_key
return scribunto_parameter_key(...)
end
local function serial_comma_join(...)
serial_comma_join = require(table_module).serialCommaJoin
return serial_comma_join(...)
end
local function shallow_copy(...)
shallow_copy = require(table_module).shallowCopy
return shallow_copy(...)
end
local function split(...)
split = require(string_utilities_module).split
return split(...)
end
local function split_alternating_runs(...)
split_alternating_runs = require(parse_utilities_module).split_alternating_runs
return split_alternating_runs(...)
end
local function split_and_process_raw_labels(...)
split_and_process_raw_labels = require(labels_module).split_and_process_raw_labels
return split_and_process_raw_labels(...)
end
local function split_on_comma(...)
split_on_comma = require(parse_utilities_module).split_on_comma
return split_on_comma(...)
end
local function tag_text(...)
tag_text = require(script_utilities_module).tag_text
return tag_text(...)
end
local function tag_transcription(...)
tag_transcription = require(script_utilities_module).tag_transcription
return tag_transcription(...)
end
local function tag_translit(...)
tag_translit = require(script_utilities_module).tag_translit
return tag_translit(...)
end
local function ulen(...)
ulen = require(string_utilities_module).len
return ulen(...)
end
local function unitalicize_brackets(...)
unitalicize_brackets = require(italics_module).unitalicize_brackets
return unitalicize_brackets(...)
end
local function upper(...)
upper = require(string_utilities_module).upper
return upper(...)
end
local function usub(...)
usub = require(string_utilities_module).sub
return usub(...)
end
local function yesno(...)
yesno = require(yesno_module)
return yesno(...)
end
local function track(page)
debug_track("quote/" .. page)
end
local function maintenance_line(text)
return '<span class="maintenance-line">(' .. text .. ")</span>"
end
local function isbn(text)
return "[[Special:BookSources/"
.. text
.. "|→ISBN]]"
.. check_isbn(
text,
' <span class="error" style="font-size:88%">Invalid ISBN</span>[[Category:Pages with ISBN errors]]'
)
end
local function issn(text)
return "[https://www.worldcat.org/issn/"
.. text
.. " →ISSN]"
.. check_issn(
text,
' <span class="error" style="font-size:88%">Invalid ISSN</span>[[Category:Pages with ISSN errors]]'
)
end
local function lccn(text)
text = text:gsub(" ", "")
if text:find("-") then
-- old-style LCCN; reformat per request by [[User:The Editor's Apprentice]]
local prefix, part1, part2 = text:match("^(.-)(%d+)%-(%d+)$")
if prefix then
if ulen(part2) < 6 then
part2 = ("0"):rep(6 - ulen(part2)) .. part2
end
text = prefix .. part1 .. part2
end
end
return "[https://lccn.loc.gov/" .. mw.uri.encode(text) .. " →LCCN]"
end
local function format_date(text)
return mw.getCurrentFrame():callParserFunction("#formatdate", text, "dmy")
end
-- Parse a raw lb= param (or nil) to individual label info objects and then concatenate them appropriately into a
-- qualifier input, respecting flags like `omit_preComma` and `omit_postSpace` in the label specs.
local function parse_and_format_labels(raw_lb, lang)
if not raw_lb then
return nil
end
local labels = split_and_process_raw_labels{labels = raw_lb, lang = lang, nocat = true}
labels = format_processed_labels{labels = labels, lang = lang, no_ib_content = true}
if labels ~= "" then -- not sure labels can be an empty string but it seems possible in some circumstances
return {labels}
end
end
-- Convert a comma-separated list of language codes to a comma-separated list of language names. `fullname` is the
-- name of the parameter from which the list of language codes was fetched.
local function format_langs(langs)
local names = {}
for i, lang in ipairs(langs) do
names[i] = lang:getCanonicalName()
end
if #names == 1 then
return names[1]
end
return serial_comma_join(names)
end
local function get_first_lang(langs)
return langs[1] or get_lang("und")
end
--[=[
Normally we parse off inline modifiers and language code prefixes in various places, e.g. he:מרים<tr:Miryem>. But we
exclude HTML entries with <span ...>, <i ...>, <br/> or similar in it, caused by wrapping an argument in {{l|...}},
{{lang|...}} or similar. Basically, all tags of the sort we parse here should consist of a less-than sign, plus letters,
plus a colon, e.g. <tr:...>, so if we see a tag on the outer level that isn't in this format, we don't try to parse it.
The restriction to the outer level is to allow generated HTML inside of e.g. qualifier modifiers, such as
foo<q:similar to {{m|fr|bar}}> (if we end up supporting such modifiers).
Also exclude things that look like URL's from being parsed as having language code prefixes.
]=]
local function val_should_not_be_parsed_for_annotations(val)
return val:find("^[^<]*<%l*[^%l:]") or val:find("^%l+://")
end
local param_mods = {
t = {
-- <t:...> and <gloss:...> are aliases.
item_dest = "gloss",
},
gloss = {},
-- Kurmanji aliases of <t:...>/<gloss:...>.
w = {
item_dest = "gloss",
},
werger = {
item_dest = "gloss",
},
tercime = {
item_dest = "gloss",
},
alt = {},
-- Kurmanji alias of <alt:...>.
cuda = {
item_dest = "alt",
},
tr = {},
ts = {},
subst = {},
sc = {type = "script"},
f = {
convert = function(arg, parse_err)
local prefix, val = arg:match("^(.-):([^ ].*)$")
if not prefix then
prefix = ""
val = arg
end
local tags, sc_code, sc = prefix:match("^(.*)/(.-)$")
if sc_code then
sc = get_script(sc_code) or
require(languages_error_module)(sc_code, parse_err, "script code", nil, "not real lang")
else
tags = prefix
end
local quals
if tags ~= "" then
quals = split_on_comma(tags)
for i, qual in ipairs(quals) do
local obj = get_lang(qual, nil, "allow etym") or get_script(qual)
quals[i] = obj or qual
end
end
return {
quals = quals,
sc = sc,
val = val,
}
end,
store = "insert",
},
q = {},
qq = {},
}
local function generate_obj_annotated_text(text, parse_err, paramname)
local obj = {}
if text:find(":[^ ]") or text:find("%[%[") then
local display, is_wikipedia_wikisource
obj.text, obj.lang, obj.link, display, is_wikipedia_wikisource =
parse_term_with_lang {
term = text,
parse_err = parse_err,
paramname = paramname
}
-- HACK: If object is a Wikipedia or Wikisource link, we need to convert it to a two-part link
-- to avoid the w: or s: prefix showing. We should probably avoid this by calling
-- language_link() (in [[Module:links]]) in format_annotated_text() instead of the hacky stuff
-- that we currently do; but we need to test that carefully to make sure it doesn't do things like
-- munge % signs.
if is_wikipedia_wikisource and not obj.text:find("%[%[") and not obj.text:find("%]%]") then
-- Don't directly set .alt, because then if the user specifies <alt:...>, it will cause an error in
-- parse_inline_modifiers().
obj.display = display
end
else
obj.text = text
obj.link = text
end
return obj
end
--[=[
Parse a textual property that may be in a foreign language or script and may be annotated with a language prefix and/or
inline modifiers. `val` is the value of the parameter and `fullname` is the name of the parameter from which the value
was retrieved. `explicit_gloss`, if specified and non-nil, overrides any gloss specified using the <t:...> or
<gloss:...> inline modifier.
If `val` is nil, the return value of this function is nil. Otherwise it is parsed for a language prefix (e.g.
'ar:مُؤَلِّف') and inline modifiers (e.g. 'ar:مُؤَلِّف<t:Author>'), and the return value is an object with the following
fields:
`lang`: The language object corresponding to the language prefix, if specified, or nil if no language prefix is
given.
`text`: The text after stripping off any language prefix and inline modifiers.
`link`: The link part of the text if it consists of a two-part link; otherwise, same as `text`.
`alt`: Display text specified using the <alt:...> modifier, if given; otherwise, nil.
`subst`: Substitutions used to generate the transliteration, in the same format as the subst= parameter.
`sc`: The script object corresponding to the <sc:...> modifier, if given; otherwise nil.
`tr`: The transliteration corresponding to the <tr:...> modifier, if given; otherwise nil.
`ts`: The transcription corresponding to the <ts:...> modifier, if given; otherwise nil.
`gloss`: The gloss/translation corresponding to the `explicit_gloss` parameter (if given and non-nil), otherwise
the <t:...> or <gloss:...> modifiers if given, otherwise nil.
`f`: Foreign versions of the text.
`q`: Left qualifiers.
`qq`: Right qualifiers.
Note that as a special case, if `val` contains HTML tags at the top level (e.g. '<span class="Arab">...</span>', as
might be generated by specifying {{lang|ar|مُؤَلِّف}}), no language prefix or inline modifiers are parsed, and the return
value has the `noscript` field set to true, which tells format_annotated_text() not to try to identify the script of
the text and CSS-tag the text accordingly, but to leave the text untagged.
This object can be passed to format_annotated_text() to format a string displaying the text (appropriately
script-tagged, unless `noscript` is set, as described above) and modifiers.
]=]
local function parse_annotated_text(val, fullname, explicit_gloss)
if not val then
return nil
end
-- When checking for inline modifiers, exclude HTML entry with <span ...>, <i ...>, <br/> or similar in it, caused
-- by wrapping an argument in {{l|...}}, {{lang|...}} or similar. Also exclude URL's from being parsed as having
-- language code prefixes. See val_should_not_be_parsed_for_annotations() for more information. If we find a
-- parameter value with top-level HTML in it, add 'noscript = true' to indicate that we should not try to do script
-- inference and tagging. (Otherwise, e.g. if you specify {{lang|ar|مُؤَلِّف}} as the author, you'll get an extra big
-- font coming from the fact that {{lang|...}} wraps the Arabic text in CSS that increases the size from the
-- default, and then we do script detection and again wrap the text in the same CSS, which increases the size even
-- more.)
if val_should_not_be_parsed_for_annotations(val) then
return {text = val, link = val, noscript = true, gloss = explicit_gloss}
end
local obj
if val:find("<") then
-- Check for inline modifier.
obj = parse_inline_modifiers(val, {
paramname = fullname,
param_mods = param_mods,
generate_obj = generate_obj_annotated_text,
})
else
obj = generate_obj_annotated_text(val, nil, fullname)
end
if explicit_gloss then
obj.gloss = explicit_gloss
end
return obj
end
local html_entity_char_to_replacement = {
["<"] = TEMP_LT,
[">"] = TEMP_GT,
["["] = TEMP_LBRAC,
["]"] = TEMP_RBRAC,
}
local function html_entity_replacement(entity, code_without_semicolon, hash, xcode, x, code)
-- Try to decode the entity. If successful, Replace certain special HTML entities (those that are bracket-like)
-- with single Unicode characters; otherwise, replace the semicolon with a special character so it won't get
-- interpreted as a delimiter.
local ch = decode_entities(entity)
if ch ~= entity then
return html_entity_char_to_replacement[ch] or code_without_semicolon .. TEMP_SEMICOLON
end
-- If the entity doesn't decode, escape it anyway iff it follows a valid format.
if hash == "" then
-- Any nonstandard MediaWiki-only entities have now been filtered off, so treat any non-ASCII characters as
-- invalid.
return xcode:match("^[^\128-\255]+$") and code_without_semicolon .. TEMP_SEMICOLON or entity
elseif x == "" then
return xcode:match("^%d+$") and code_without_semicolon .. TEMP_SEMICOLON or entity
end
return code:match("^%x+$") and code_without_semicolon .. TEMP_SEMICOLON or entity
end
local html_entity_replacement_to_char = {
[TEMP_LT] = "<",
[TEMP_GT] = ">",
[TEMP_LBRAC] = "[",
[TEMP_RBRAC] = "]",
[TEMP_SEMICOLON] = ";",
}
local function undo_html_entity_replacement(txt)
-- Pattern covers everything in html_entity_replacement_to_char.
return (txt:gsub("\239\191[\177-\181]", html_entity_replacement_to_char))
end
-- NOTE: We try hard to optimize this function for the common cases and avoid loading [[Module:parse utilities]]
-- in such cases. The cases we can handle without loading [[Module:parse utilities]] are single values (no
-- semicolons present) without inline modifiers or language prefixes, and multi-entity values (semicolons present)
-- without (a) brackets of any kind (including parens, braces and angle brackets; angle brackets typically indicate
-- inline modifiers and other brackets may protect a semicolon from being interpreted as a delimiter);
-- (b) ampersands (which may indicate HTML entities, which protect a semicolon from being interpreted as a
-- delimiter); and (c) colons not followed by a space (which may indicate a language prefix).
local function generate_obj_multivalued_annotated_text(text, parse_err, paramname, no_undo_html_entity_replacement)
local obj = generate_obj_annotated_text(text, parse_err, paramname)
if not no_undo_html_entity_replacement then
obj.text = undo_html_entity_replacement(obj.text)
obj.link = undo_html_entity_replacement(obj.link)
end
return obj
end
--[=[
Similar to parse_annotated_text() but the parameter value may contain multiple semicolon-separated entities, each with
their own inline modifiers. Some examples:
* mainauthor=Paula Pattengale; Terea Sonsthagen
* author=Katie Brick; J. Cody Nielsen; Greg Jao; Eric Paul Rogers; John A. Monson
* author=Suzanne Brockmann; Patrick G. Lawlor (Patrick Girard); Melanie Ewbank
* author=G Ristori; et al.
* author=Jason Scott; zh:王晰宁<t:Wang Xining>
* editors=zh:包文俊; zh:金心雯
* quotee=zh:張福運<t:Chang Fu-yun>; zh:張景文<t:Chang Ching-wen>
There may be embedded semicolons within brackets, braces or parens that should not be treated as delimiters, e.g.:
* author=Oliver Optic [pseudonym; {{w|William Taylor Adams}}]
* author=author=Shannon Drake (pen name; {{w|Heather Graham Pozzessere}})
* author=James (the Elder;) Humphrys
There may also be HTML entities with semicolons in them:
* author=[{{w|Gilbert Clerke}}]
* 2ndauthor=Martin Biddle & Sally Badham
* author=Peter Christen Asbjørnsen
There may be both embedded semicolons and HTML entities with semicolons in them:
* author=[{{w|Voltaire}} [pseudonym; François-Marie Arouet]]
In general we want to treat [ like an opening bracket and ] like a closing bracket. Beware that they may be
mismatched:
* author=Anonymous [{{w|Karl Maria Kertbeny}}]
Here, `val` is the value of the parameter and `fullname` is the name of the parameter from which the value was
retrieved. `explicit_gloss`, if specified and non-nil, overrides any gloss specified using the <t:...> or <gloss:...>
inline modifier, and `explicit_gloss_fullname` is the name of the parameter from which this value was retrieved. (If
`explicit_gloss` is specified and multiple values were seen, an error results.)
Return value is a list of objects of the same sort as returned by parse_annotated_text().
]=]
local function parse_multivalued_annotated_text(val, fullname, explicit_gloss, explicit_gloss_fullname)
if not val then
return nil
end
-- NOTE: In the code that follows, we use `entity` most of the time to refer to one of the semicolon-separated
-- values in the multivalued param. Entities are most commonly people (typically authors, editors, translators or
-- the like), but may be the names of publishers, locations, or other entities. "Entity" can also refer to HTML
-- entities; in the places where this occurs, the variable name contains 'html' in it.
local splitchar, english_delim
if val:find("^,") then
splitchar = ","
english_delim = "comma"
val = val:gsub("^,", "")
else
splitchar = ";"
english_delim = "semicolon"
end
-- Optimization #1: No semicolons/commas or angle brackets (indicating inline modifiers).
if not val:find("[<" .. splitchar .. "]") then
if val_should_not_be_parsed_for_annotations(val) then
return {{text = val, link = val, noscript = true}}
else
return {generate_obj_multivalued_annotated_text(val, nil, fullname, "no undo html entity replacement")}
end
end
-- Optimization #2: Semicolons/commas but no angle brackets (indicating inline modifiers), braces, brackets, or
-- parens (any of which would protect the semicolon/comma from interpretation as a delimiter), and no ampersand
-- (which might indicate an HTML entity with a terminating semicolon, which should not be interpreted as a
-- delimiter).
if not val:find("[<>%[%](){}&]") then
local entity_objs = {}
for entity in gsplit(val, "%s*" .. splitchar .. "%s*") do
if val_should_not_be_parsed_for_annotations(entity) then
insert(entity_objs, {
text = entity,
link = entity,
noscript = true
})
else
insert(entity_objs, generate_obj_multivalued_annotated_text(entity, nil, fullname, "no undo html entity replacement"))
end
end
return entity_objs
end
-- Escape HTML entities, and get rid of directionality markers.
local amp = val:find("&", nil, true)
if amp then
-- The pattern is more permissive than the usual entity pattern, as MediaWiki has some nonstandard entities
-- that have non-ASCII characters in their codes.
val = val:gsub("((&(#?)(([xX]?)([%w\128-\255]+)));)", html_entity_replacement)
end
-- Pattern covers left-to-right (U+200E) and right-to-left (U+200F).
val = val:gsub("\226\128[\142\143]", "")
-- Parse balanced segment runs, treating HTML entities for left and right bracket and left and right angle bracket
-- as matching literal versions of the same characters.
local entity_runs = parse_multi_delimiter_balanced_segment_run(
val,
{{"[" .. TEMP_LBRAC, "]" .. TEMP_RBRAC}, {"(", ")"}, {"{", "}"}, {"<" .. TEMP_LT, ">" .. TEMP_GT}},
true
)
if type(entity_runs) == "string" then
local undo_val = undo_html_entity_replacement(val)
-- Parse error due to unbalanced delimiters. Don't throw an error here; instead, don't attempt to parse off
-- any annotations, but return the value directly, maybe allowing script tagging (not allowing it if it appears
-- the text is already script-tagged).
return {{text = undo_val, link = undo_val, noscript = not not val_should_not_be_parsed_for_annotations(val)}}
end
-- Split on semicolon (or comma), possibly surrounded by whitespace.
local separated_groups = split_alternating_runs(entity_runs, "%s*" .. splitchar .. "%s*")
-- Process each value.
local entity_objs = {}
for _, entity_group in ipairs(separated_groups) do
-- Rejoin runs that don't involve <...>.
local j = 2
while j <= #entity_group do
if not entity_group[j]:find("^<.*>$") then
entity_group[j - 1] = entity_group[j - 1] .. entity_group[j] .. entity_group[j + 1]
remove(entity_group, j)
remove(entity_group, j)
else
j = j + 2
end
end
local oneval = undo_html_entity_replacement(concat(entity_group))
-- When checking for inline modifiers, exclude HTML entry with <span ...>, <i ...>, <br/> or similar in it,
-- caused by wrapping an argument in {{l|...}}, {{lang|...}} or similar. Also exclude URL's from being parsed
-- as having language code prefixes. This works analogously to parse_annotated_text(); see there for more.
if val_should_not_be_parsed_for_annotations(oneval) then
insert(entity_objs, {
text = oneval,
link = oneval,
noscript = true
})
else
local obj
if #entity_group > 1 then
-- Check for inline modifier.
obj = parse_inline_modifiers_from_segments({
group = entity_group,
arg = oneval,
props = {
paramname = fullname,
param_mods = param_mods,
generate_obj = generate_obj_multivalued_annotated_text,
},
})
else
obj = generate_obj_multivalued_annotated_text(entity_group[1], nil, fullname)
end
insert(entity_objs, obj)
end
end
if explicit_gloss then
if #entity_objs > 1 then
error(
(
"Can't specify |%s= along with multiple %s-separated entities in |%s=; use the <t:...> "
.. "inline modifier attached to the individual entities"
):format(explicit_gloss_fullname, english_delim, fullname)
)
end
entity_objs[1].gloss = explicit_gloss
end
return entity_objs
end
--[=[
Format a text property that may be in a foreign language or script, along with annotations. This is conceptually
similar to the full_link() function in [[Module:links]], but displays the annotations in a different format that is
more appropriate for bibliographic entries. The output looks like this:
TEXT [TRANSLIT /TRANSCRIPTION/, GLOSS]
`textobj` is as returned by parse_annotated_text(). `tag_text_func`, if supplied, is a function of one argument to further
wrap the text after it has been processed and CSS-tagged appropriately, directly before insertion. `tag_gloss_func` is a
similar function for the gloss.
]=]
local function format_annotated_text(textobj, tag_text_func, tag_gloss_func)
if not textobj then
return nil
end
local text, link = textobj.text, textobj.link
local subst, tr, ts, f, gloss = textobj.subst, textobj.tr, textobj.ts, textobj.f, textobj.gloss
-- Retrieve the display text, either specified by the user (.alt) or by parse_annotated_text() (.display) when a
-- Wikipedia link is given.
local alt = textobj.alt or textobj.display
if alt then
if link:find("%[%[") or link:find("%]%]") then
local errmsg = ("Can't currently handle embedded links in '%s', with <alt:...> text '%s'"):format(link, alt)
error(escape_wikicode(errmsg))
end
text = ("[[%s|%s]]"):format(link, alt)
end
-- See above for `noscript`, meaning HTML was found in the text value, probably generated using {{lang|...}}.
-- {{lang}} already script-tags the text and processes embedded language links, so we don't want to do it again (in
-- fact, the code below within the if-clause is similar to what {{lang}} does). In such a case, an explicit language
-- won't be available and find_best_script_without_lang() may not be accurate, so we can't do automatic transliteration.
if not textobj.noscript then
local lang = textobj.lang
-- As an optimization, don't do script detection on an argument that contains only ASCII.
local sc = textobj.sc
or lang and lang:findBestScript(text)
or not text:find("^[ -~]$") and find_best_script_without_lang(text)
or nil
-- As an optimization, don't do any of the following if there's no language, script, translit or transcription,
-- as will be the case with simple ASCII values.
if lang or sc or tr or ts then
if not lang then
lang = get_lang("und")
end
if tr == "-" then
tr = nil
elseif not tr and sc and not sc:getCode():find("Lat") then -- Latn, Latf, Latg, pjt-Latn
-- might return nil
local text_for_tr = text
if subst then
text_for_tr = apply_subst(text_for_tr, subst)
else
text_for_tr = remove_links(text)
end
tr = (lang:transliterate(text_for_tr, sc))
end
if text:find("%[%[") then
-- FIXME: embedded_language_links() replaces % signs with their URL-encoded equivalents,
-- which messes up URL's that may be present (e.g. if chapterurl= is given). IMO this
-- should not happen, and embedded_language_links() should do nothing if no embedded links
-- are present. To work around this, only call embedded_language_links() when there are
-- embedded links present.
text = embedded_language_links({
term = text,
lang = lang,
sc = sc,
})
end
if lang:getCode() ~= "und" or sc:getCode() ~= "Latn" then
text = tag_text(text, lang, sc)
end
if tr then
-- Should we link to the transliteration of languages with lang:link_tr()? Probably not because `text` is not
-- likely to be a term that has an entry.
tr = tag_translit(tr, lang, "usex")
end
if ts then
ts = tag_transcription(ts, lang, "usex")
end
end
end
text = unitalicize_brackets(text)
if tag_text_func then
text = tag_text_func(text)
end
local parts = {}
if textobj.q then
insert(parts, format_qualifier(textobj.q) .. " ")
end
insert(parts, text)
if tr or ts or f or gloss then
insert(parts, SPACE_LBRAC)
local subparts = {}
if tr or ts then
local tr_ts
if ts then
ts = "/" .. ts .. "/"
end
if tr and ts then
tr_ts = tr .. " " .. ts
else
tr_ts = tr or ts
end
insert(subparts, tr_ts)
end
if f then
for _, ff in ipairs(f) do
local sc = ff.sc
local lang
if not sc and ff.quals then
local qual = ff.quals[1]
if type(qual) == "string" then
-- do nothing; we'll do script detection farther down
elseif qual:hasType("script") then
sc = qual
else -- language
sc = qual:findBestScript(ff.val)
lang = qual
end
end
if not lang then
lang = get_lang("und")
end
sc = sc or find_best_script_without_lang(ff.val)
local val = embedded_language_links({
term = ff.val,
lang = lang,
sc = sc,
})
if lang:getCode() ~= "und" or sc:getCode() ~= "Latn" then
val = tag_text(val, lang, sc)
end
local qual_prefix
if ff.quals then
for i, qual in ipairs(ff.quals) do
if type(qual) ~= "string" and (qual:hasType("script") or qual:hasType("language")) then
ff.quals[i] = qual:getCanonicalName()
end
end
qual_prefix = concat(ff.quals, "/") .. ": "
else
qual_prefix = ""
end
insert(subparts, qual_prefix .. val)
end
end
if gloss then
gloss = '<span class="e-translation">' .. gloss .. "</span>"
gloss = unitalicize_brackets(gloss)
if tag_gloss_func then
gloss = tag_gloss_func(gloss)
end
insert(subparts, gloss)
end
insert(parts, concat(subparts, ", "))
insert(parts, RBRAC)
end
if textobj.qq then
insert(parts, " " .. format_qualifier(textobj.qq))
end
return concat(parts)
end
--[=[
Format a multivalued text property that may be in a foreign language or script, along with annotations. This is the
multivalued analog to format_annotated_text(), and formats each individual entity using format_annotated_text(),
joining the results with `delimiter`, which defaults to ", ". It `delimiter` is "and" or "or", join the results using
serial_comma_join() with the specified conjunction.
`textobjs` is as returned by parse_multivalued_annotated_text(). `tag_text_func` and `tag_gloss_func` are as in
format_annotated_text().
]=]
local function format_multivalued_annotated_text(textobjs, delimiter, tag_text_func, tag_gloss_func)
if not textobjs then
return nil
end
if #textobjs == 1 then
return format_annotated_text(textobjs[1], tag_text_func, tag_gloss_func)
end
local parts = {}
for _, textobj in ipairs(textobjs) do
insert(parts, format_annotated_text(textobj, tag_text_func, tag_gloss_func))
end
-- Change delimiter to semicolon if the items themselves contain commas (e.g., in ", Jr.")
local use_semicolon = false
for _, obj in ipairs(textobjs) do
-- Use remove_links to ignore commas in links
if obj.text and remove_links(obj.text):find(", ") then
use_semicolon = true
break
end
end
local n = #parts
if n > 0 and parts[n]:match("^'*et al[.']*$") then
-- Special handling for 'et al.'
parts[n] = "''et al.''"
if n == 2 then
-- author et al.
return concat(parts, " ")
else
-- author 1, author 2, et al.
return concat(parts, (use_semicolon and "; ") or ", ")
end
if delimiter == "and" or delimiter == "or" then
delimiter = ", "
end
return concat(parts, delimiter)
end
if delimiter == "and" or delimiter == "or" then
return serial_comma_join(parts, {conj = delimiter})
end
return concat(parts, delimiter or ((use_semicolon and "; ") or ", "))
end
-- Fancy version of ine() (if-not-empty). Converts empty string to nil, but also strips leading/trailing space.
local function ine(arg)
if not arg then
return nil
elseif type(arg) ~= "string" then
return arg
end
arg = mw.text.trim(arg)
if arg == "" then
return nil
end
return arg
end
local abbrs = {
["a."] = {anchor = "a.", full = "ante"},
["c."] = {anchor = "c.", full = "circa"},
["p."] = {anchor = "p.", full = "post"},
}
-- Process prefixes 'a.' (ante), 'c.' (circa) and 'p.' (post) at the beginning of an arbitrary date or year spec.
-- Returns two values, the formatted version of the prefix and the date spec minus the prefix. If no prefix is found,
-- returns an empty string and the full date.
local function process_ante_circa_post(date)
local prefix = usub(date, 1, 2)
local abbr = abbrs[prefix]
local abbr_prefix = ""
if abbr then
abbr_prefix = "''[[Appendix:Glossary#"
.. abbr.anchor
.. '|<abbr title="'
.. abbr.full
.. '">'
.. abbr.anchor
.. "</abbr>]]'' "
-- Remove lowercase letter, period, and space from beginning of date parameter.
date = ugsub(date, "^%l%.%s*", "")
end
return abbr_prefix, date
end
-- Format the arguments that specify the date of the quotation. These include the following:
-- |date=: The date. If |start_date= is given, this is the end date.
-- |year=, |month=: Year and month of quotation date or end of range, if |date= isn't given.
-- |start_date=: The start date, to specify a range.
-- |start_year=, |start_month=: Year and month of start of range, if |start_date= isn't given.
-- |accessdate=: Date a website was accessed; processed if no other date was given.
-- |nodate=: Indicate that no date is present; otherwise a maintenance line will be displayed if there is no date.
--
-- If `parampref` and/or `paramsuf` are given, this modifies all the date arguments accordingly. For example, if
-- `parampref` == "orig" and `paramsuf` is omitted, the date is specified using |origdate= or |origyear=/|origmonth=,
-- and the start of the range is |origstart_date=, etc. Similarly, if `parampref` is omitted and `paramsuf` is
-- "_published", the date is specified using |date_published= or |year_published=/|month_published=, and the start of
-- the range is |start_date_published=, etc.
--
-- `a` and `get_full_paramname` are functions with the same interpretation as the local functions of the same name in
-- source(). These are used to fetch parameters and get their full names. Note that this may cause all arguments to
-- have an index added to them (|date2=, |year2=, |month2=, etc.).
--
-- `alias_map` is as in source() and is used to map canonical arguments to their aliases when aliases were used.
--
-- If `bold_year` is given, displayed years are boldfaced unless boldface is present in the parameter value.
--
-- If `maintenance_line_no_date` is specified, it should be a string that will be returned if no date is found (i.e.
-- neither |date= nor |year=, or their appropriate equivalents per `parampref` and `paramsuf`, are specified, and
-- neither |nodate= is given to indicate that there is no date, or |accessdate= is given).
--
-- Returns two values: the formatted date and a boolean indicating whether to add a maintenance category
-- [[:Category:Requests for date in LANG entries]]. The first return value will be nil if nothing is to be added
-- (in which case the scond return value will always be nil).
local function format_date_args(
a,
get_full_paramname,
alias_map,
parampref,
paramsuf,
bold_year,
maintenance_line_no_date,
year_last
)
local output = {}
parampref = parampref or ""
paramsuf = paramsuf or ""
local function getp(param)
return a(parampref .. param .. paramsuf)
end
local function pname(param)
local fullname = get_full_paramname(parampref .. param .. paramsuf)
return alias_map[fullname] or fullname
end
-- Format `timestamp` (a timestamp referencing a date) according to the spec in `code`. `param` is the base name of
-- the parameter from which the timestamp was fetched, for error messages.
local function format_date_with_code(code, timestamp, param)
local language = mw.getContentLanguage()
local ok, date = pcall(language.formatDate, language, code, timestamp)
if ok then
return date
else
-- All the formats used in format_date_args() are fine, so the timestamp must be at fault.
error(
(
"Timestamp |%s=%s (possibly canonicalized from its original format) could not be parsed; see the "
.. "[[mw:Help:Extension:ParserFunctions##time|documentation for the #time parser function]]"
):format(pname(param), tostring(timestamp))
)
end
end
-- Try to figure out if the given timestamp has the day of the month explicitly given. We use the following
-- algorithm:
-- 1. Format as year-month-day; if the day is not 1, the day was explicitly given, since if only the year/month are
-- given, the day shows up as 1.
-- 2. If the day shows up as 1 and there isn't a 1 or 01 in the timestamp, the day wasn't explicitly given.
-- 3. Otherwise, if there are three separate numbers (e.g. 2022-07-01), or two separate numbers plus a capitalized
-- letter (taken as an English month, e.g. 2022 July 1), the day was explicitly given, otherwise not.
--
-- `param` is the base name of the parameter from which the timestamp was fetched.
local function date_has_day_specified(timestamp, param)
local day = format_date_with_code("j", timestamp, param)
if day ~= "1" then
return true
end
local english_month = timestamp:find("%u")
local canon_timestamp = mw.text.trim((timestamp:gsub("%D+", " ")))
local seen_nums = split(canon_timestamp, " ", true)
local saw_one = false
for _, num in ipairs(seen_nums) do
if num == "1" or num == "01" then
saw_one = true
break
end
end
if not saw_one then
return false
end
return #seen_nums >= 3 or english_month and #seen_nums >= 2
end
-- Format a date with boldfaced year, as e.g. '''2023''' August 3. `explicit_day_given` indicates whether to include
-- the day; if false, the return value will be e.g. '''2023''' August. `date_param` is the base name of the param
-- from which the date was fetched, for error messages.
local function format_bold_date(date, explicit_day_given, date_param)
local day_month_code = explicit_day_given and "j F" or "F"
local month_day_code = explicit_day_given and "F j" or "F"
if year_last then
if bold_year then
-- This formats like "3 August '''2023'''" (or "August '''2023'''" if day not explicitly given).
return format_date_with_code(day_month_code .. " '''Y'''", date, date_param)
else
-- This formats like "3 August 2023" (or "August 2023" if day not explicitly given).
return format_date_with_code(day_month_code .. " Y", date, date_param)
end
else
if bold_year then
-- This formats like "'''2023''' August 3" (or "'''2023''' August" if day not explicitly given).
return format_date_with_code("'''Y''' " .. month_day_code, date, date_param)
else
-- This formats like "2023 August 3" (or "2023 August" if day not explicitly given).
return format_date_with_code("Y " .. month_day_code, date, date_param)
end
end
end
-- The formatDate method of the mw.language object behaves like the {{#time:}} parser function, which doesn't
-- accept the formats "monthday monthname, year" or "year monthname monthday", but outputs garbage when it receives
-- them, behavior inherited from PHP. {{#formatdate:}} magic word is more forgiving. Fix dates so that, for
-- instance, the |date= parameter of {{quote-journal}} (which uses this code) and the |accessdate= parameter (which
-- uses {{#formatdate:}}) accept similar date formats. See:
-- * [[mw:Extension:Scribunto/Lua_reference_manual#mw.language:formatDate]]
-- * [[mw:Help:Extension:ParserFunctions##time]]
-- * [[mw:Help:Magic_words#Formatting]]
-- `date` is the date spec from the user, which is assumed to come from a parameter whose base name ends in "date";
-- `parampref` is the prefix added to "date" to get the parameter name.
local function fix_date(date, param_pref)
if tonumber(date) ~= nil then
error(
("|%s= should contain a full date (year, month, day of month); use |%s= for year"):format(
pname(param_pref .. "date"),
pname(param_pref .. "year")
)
)
elseif date and date:find("%s*%a+,%s*%d+%s*$") then
error(
("|%s= should contain a full date (year, month, day of month); use |%s=, |%s= for month and year"):format(
pname(param_pref .. "date"),
pname(param_pref .. "month"),
pname(param_pref .. "year")
)
)
end
if date then
-- Commas are replaced with spaces to prevent parsing issues
local fixed_date = ugsub(date, ", *", " ")
return (ugsub(ugsub(fixed_date or date, "(%d+ %a+),", "%1"), "^(%d%d%d%d) (%a+ %d%d?)$", "%2 %1"))
end
end
local start_date, date = fix_date(getp("start_date"), "start_"), fix_date(getp("date"), "")
local year = getp("year")
local month = getp("month")
local start_year = getp("start_year")
local start_month = getp("start_month")
if date and year then
error(("Only one of |%s= or |%s= should be specified"):format(pname("date"), pname("year")))
end
if date and month then
error(
("|%s= should only be specified in conjunction with |%s=, not with |%s="):format(
pname("month"),
pname("year"),
pname("date")
)
)
end
if start_date and start_year then
error(("Only one of |%s= or |%s= should be specified"):format(pname("start_date"), pname("start_year")))
end
if start_date and start_month then
error(
("|%s= should only be specified in conjunction with |%s=, not with |%s="):format(
pname("start_month"),
pname("start_year"),
pname("start_date")
)
)
end
if (start_date or start_year) and not (date or year) then
error(
("|%s= or |%s=/|%s= cannot be specified without specifying |%s= or |%s=/|%s="):format(
pname("start_date"),
pname("start_year"),
pname("start_month"),
pname("date"),
pname("year"),
pname("month")
)
)
end
local dash = " – "
local day_explicitly_given = date and date_has_day_specified(date, "date")
local start_day_explicitly_given = start_date and date_has_day_specified(start_date, "start_date")
-- Format a date with boldfaced year, as e.g. '''2023''' August 3 (if `explicit_day_given` specified) or
-- '''2023''' August (if `explicit_day_given` not specified). If no date specified, fall back to formatting based
-- on the year and (optionally) month params given in `yearobj` and `monthobj`, boldfacing the year if not already.
-- `date_param` is the base name of the param from which the date was fetched, for error messages.
local function format_date_or_year_month(date, yearobj, monthobj, explicit_day_given, date_param)
if date then
return format_bold_date(date, explicit_day_given, date_param)
else
-- Boldface a year spec if it's not already boldface.
if bold_year and not yearobj.text:find("'''") then
-- Clone the year object before modifying it because we may use it later to check against the current
-- year (if we're dealing with start_year).
yearobj = shallow_copy(yearobj)
yearobj.text = "'''" .. yearobj.text .. "'''"
if yearobj.alt then
yearobj.alt = "'''" .. yearobj.alt .. "'''"
end
end
if year_last then
return (monthobj and format_annotated_text(monthobj) .. " " or "") .. format_annotated_text(yearobj)
else
return format_annotated_text(yearobj) .. (monthobj and " " .. format_annotated_text(monthobj) or "")
end
end
end
local yearobj = parse_annotated_text(year, pname("year"))
local monthobj = parse_annotated_text(month, pname("month"))
local start_yearobj = parse_annotated_text(start_year, pname("start_year"))
local start_monthobj = parse_annotated_text(start_month, pname("start_month"))
if start_yearobj then
local abbr_prefix
abbr_prefix, start_yearobj.text = process_ante_circa_post(start_yearobj.text)
start_yearobj.link = select(2, process_ante_circa_post(start_yearobj.link))
insert(output, abbr_prefix)
elseif yearobj then
local abbr_prefix
abbr_prefix, yearobj.text = process_ante_circa_post(yearobj.text)
yearobj.link = select(2, process_ante_circa_post(yearobj.link))
insert(output, abbr_prefix)
end
if start_date or start_year then
local cur_year = yearobj and yearobj.text or format_date_with_code("Y", date, "date")
local cur_month = monthobj and monthobj.text or date and format_date_with_code("F", date, "date") or nil
local cur_day = date and day_explicitly_given and format_date_with_code("j", date, "date") or nil
local beg_year = start_yearobj and start_yearobj.text or format_date_with_code("Y", start_date, "start_date")
local beg_month = start_monthobj and start_monthobj.text
or start_date and format_date_with_code("F", start_date, "start_date")
or nil
local beg_day = start_date
and start_day_explicitly_given
and format_date_with_code("j", start_date, "start_date")
or nil
if year_last then
if beg_day then
insert(output, beg_day .. " " .. beg_month)
else
insert(output, beg_month)
end
if beg_year ~= cur_year then
insert(output, " " .. beg_year)
end
else
insert(output, format_date_or_year_month(
start_date,
start_yearobj,
start_monthobj,
start_day_explicitly_given,
"start_date"
))
end
if cur_year ~= beg_year then
-- Different years; insert current date in full.
if beg_month or cur_month then
insert(output, dash)
else
insert(output, "–")
end
insert(output, format_date_or_year_month(date, yearobj, monthobj, day_explicitly_given, "date"))
elseif cur_month and cur_month ~= beg_month then
local month_ins = monthobj and format_annotated_text(monthobj) or cur_month
-- Same year but different months; insert current month and (if available) current day.
if cur_day then
insert(output, dash)
if year_last then
insert(output, cur_day .. " " .. month_ins)
else
insert(output, month_ins .. " " .. cur_day)
end
else
if beg_day then
insert(output, dash)
else
insert(output, "–")
end
insert(output, month_ins)
end
elseif cur_day and cur_day ~= beg_day then
-- Same year and month but different days; insert current day.
insert(output, "–")
insert(output, cur_day)
else
-- Same year, month and day; or same year and month, and day not available; or same year, and month and
-- day not available. Do nothing. FIXME: Should we throw an error?
end
if year_last and beg_year == cur_year then
if cur_month then
insert(output, " " .. cur_year)
else
insert(output, " " .. cur_year)
end
end
elseif date or yearobj then
insert(output, format_date_or_year_month(date, yearobj, monthobj, day_explicitly_given, "date"))
elseif not maintenance_line_no_date then
-- Not main quote date. Return nil, caller will handle.
return nil, nil
elseif not getp("nodate") then
local accessdate = getp("accessdate")
if accessdate then
local explicit_day_given = date_has_day_specified(accessdate, "accessdate")
insert(output, format_bold_date(accessdate, explicit_day_given, "accessdate") .. " (last accessed)")
else
if mw.title.getCurrentTitle().namespace ~= 10 then
return maintenance_line(maintenance_line_no_date), true
end
return nil, nil
end
end
return ine(concat(output)), nil
end
local function tag_with_cite(txt)
return "<cite>" .. txt .. "</cite>"
end
-- Display the source line of the quote, above the actual quote text. This contains the majority of the logic of this
-- module (formerly contained in {{quote-meta/source}}).
function export.source(args, alias_map, format_as_cite, other_controls)
local tracking_categories = {}
local argslang = args[1] or args.lang
if not argslang then
-- For the moment, only trigger an error on mainspace pages and
-- other pages that are not user pages or pages containing discussions.
-- These are the same pages that appear in the appropriate tracking
-- categories. User and discussion pages have not generally been
-- fixed up to include a language code and so it's more helpful
-- to use a maintenance line than signal an error.
local current_title = mw.title.getCurrentTitle()
if not (current_title.namespace == 10 or page_should_be_ignored(current_title.fullText)) then
require(languages_error_module)(nil, 1)
end
end
-- Given a canonical param, convert it to the original parameter specified by the user (which may have been an
-- alias).
local function alias(param)
return alias_map[param] or param
end
-- Wrap contributor names (authors, editors, translators, compilers, etc.) and the bolded publication year in a
-- span so they can be styled distinctly (e.g. Kurdish Wiktionary's "j-pewist" highlighting).
local function j_lazim(text)
if not text then
return nil
end
return '<span class="j-pewist">' .. text .. "</span>"
end
local output, sep = {}
local overrides = other_controls and other_controls.overrides or {}
-- Add text to the output. The text goes into a list, and we concatenate all the list components together at the
-- end. To make it easier to handle comma-separated items, we keep track (in `sep`) of the separator (if any) that
-- needs to be inserted before the next item added. For example, if we're in the "newversion" code (ind ~= ""), and
-- there's no title and no URL, then the first time we add anything after the title, we don't want to add a
-- separating comma because the preceding text will say "republished " or "republished as " or "translated as " or
-- similar. In all- other cases, we do want to add a separating comma. The bare add() function reset the separator
-- to be nothing, while the add_with_sep() function resets the separator to be the value of `next_sep` (defaulting
-- to ", "), so the next time around we do add a comma to separate `text` from the preceding piece of text.
local function add(text)
if sep then
insert(output, sep)
end
insert(output, text)
sep = nil
end
local function add_with_sep(text, next_sep)
add(text)
sep = next_sep or ", "
end
-- FIXME: This is all very over-engineered, which makes it slow and memory-inefficient.
-- Return a function that generates the actual parameter name associated with a base param (e.g. "author", "last").
-- The actual parameter name may have an index added (an empty string for the first set of params, e.g. author=,
-- last=, or a numeric index for further sets of params, e.g. author2=, last2=, etc.).
local function make_get_full_paramname(ind)
return function(param)
return param .. ind
end
end
-- Function to fetch the actual parameter name associated with a base param (see make_get_full_paramname() above).
-- Assigned at various times below by calling make_get_full_paramname(). We do it this way so that we can have
-- wrapper functions that access params and define them only once.
local get_full_paramname
-- Return two values: the value of a parameter given the base param name (which may have a numeric index added),
-- and the parameter name from which the value was fetched (which may be an alias, i.e. you can't necessarily fetch
-- the parameter value from args[] given this name). The base parameter can be a list of such base params, which
-- are checked in turn, or nil, in which case nil is returned.
local function a_with_name(param)
if not param then
return nil
elseif type(param) ~= "table" then
local fullname = get_full_paramname(param)
return args[fullname], alias(fullname)
end
for _, par in ipairs(param) do
local val, fullname = a_with_name(par)
if val then
return val, alias(fullname)
end
end
return nil
end
-- Fetch the value of a parameter given the base param name (which may have a numeric index added). The base
-- parameter can be a list of such base params, which are checked in turn, or nil, in which case nil is returned.
local function a(param)
return (a_with_name(param))
end
-- Identical to a_with_name(param) except that it verifies that no space is present. Should be used for URL's.
local function aurl_with_name(param)
local value, fullname = a_with_name(param)
if value and value:find(" ") and not value:find("%[") then
error(("URL not allowed to contain a space, but saw |%s=%s"):format(fullname, value))
end
return value, fullname
end
-- Identical to a(param) except that it verifies that no space is present. Should be used for URL's.
local function aurl(param)
return (aurl_with_name(param))
end
-- Convenience function to fetch a parameter that may be in a foreign language or text (and may consequently have
-- a language prefix and/or inline modifiers), parse the annotations and convert the result into a formatted string.
-- This is the same as parse_and_format_annotated_text() below but also returns the full param name as the second
-- return value.
local function parse_and_format_annotated_text_with_name(param, tag_text_func, tag_gloss_func)
local val, fullname = a_with_name(param)
local obj = parse_annotated_text(val, fullname)
return format_annotated_text(obj, tag_text_func, tag_gloss_func), fullname
end
-- Convenience function to fetch a parameter that may be in a foreign language or text (and may consequently have
-- a language prefix and/or inline modifiers), parse the modifiers and convert the result into a formatted string.
-- This is a wrapper around parse_annotated_text() and format_annotated_text(). `param` is the base parameter name (see
-- a_with_name()), `tag_text_func` is an optional function to tag the parameter text after all other processing (e.g.
-- wrap in <cite>...</cite> tags), and `tag_gloss_func` is a similar function for the parameter translation/gloss.
local function parse_and_format_annotated_text(param, tag_text_func, tag_gloss_func)
return (parse_and_format_annotated_text_with_name(param, tag_text_func, tag_gloss_func))
end
-- Convenience function to fetch a multivalued parameter that may be in a foreign language or text (and may
-- consequently have a language prefix and/or inline modifiers), parse the modifiers and convert the result into a
-- formatted string. This is the multivalued analog to parse_and_format_annotated_text_with_name() and returns two
-- values, the formatted string and the full name of the parameter fetched. `delimiter` is as in
-- format_multivalued_annotated_text().
local function parse_and_format_multivalued_annotated_text_with_name(param, delimiter, tag_text_func, tag_gloss_func)
local val, fullname = a_with_name(param)
local objs = parse_multivalued_annotated_text(val, fullname)
local num_objs = objs and #objs or 0
return format_multivalued_annotated_text(objs, delimiter, tag_text_func, tag_gloss_func), fullname, num_objs
end
-- Convenience function to fetch a multivalued parameter that may be in a foreign language or text (and may
-- consequently have a language prefix and/or inline modifiers), parse the modifiers and convert the result into a
-- formatted string. This is the multivalued analog to parse_and_format_annotated_text(). `delimiter` is as in
-- format_multivalued_annotated_text().
local function parse_and_format_multivalued_annotated_text(param, delimiter, tag_text_func, tag_gloss_func)
return (parse_and_format_multivalued_annotated_text_with_name(param, delimiter, tag_text_func, tag_gloss_func))
end
-- This determines whether to display "Mary Bloggs, transl." (if there's no author preceding) or "translated by
-- Mary Bloggs" (if there's an author preceding).
local author_outputted = false
-- When formatting as a citation, the priority is to display a name and a date before the book/chapter title
-- this tracks whether or not the author/date has been displayed
local date_outputted, formatted_date, formatted_origdate = false
local function add_date(no_paren)
if not date_outputted then
if no_paren then
sep = ", "
else
sep = " "
end
if formatted_date then
if no_paren then
add(formatted_date)
else
add("(" .. formatted_date .. ")")
end
end
if formatted_origdate then
add(SPACE_LBRAC .. formatted_origdate .. RBRAC)
end
sep = ", "
date_outputted = true
end
end
local function is_anonymous(val)
return val:match("^[Aa]non[yî]mo?u?s?$") or val:match("^[Aa]non%.?$")
end
-- Add a formatted author (whose values may be specified using `author` or, for compatibility purposes, split
-- among various parameters):
-- * `author` is the value of the author param (e.g. "author", "author2" or "2ndauthor"), and `author_fullname` is
-- the full parameter name holding that value;
-- * `trans_author` is the optional value of the param holding the gloss/translation of the author, and
-- `trans_author_fullname` is the full parameter name holding that value (or nil for no such parameter);
-- * `authorlink` is the value of the authorlink param, which holds the Wikipedia link of the author(s) in `author`,
-- and `authorlink_fullname` is the full parameter name holding that value;
-- * `trans_authorlink` is the optional value of the param holding the Wikipedia link of the gloss/translation of
-- the author, and `trans_authorlink_fullname` is the full parameter name holding that value (or nil for no such
-- parameter);
-- * `first` is the value of the parameter holding the first name of the author, and `first_fullname` is the full
-- parameter name holding that value;
-- * `trans_first` is the value of the corresponding parameter holding the gloss/translation of the first name
-- (e.g. "trans-first"), and `trans_first_fullname` is the full parameter name holding that value (or nil for
-- no such parameter);
-- * `last` is the value of the parameter holding the last name of the author, and `last_fullname` is the full
-- parameter name holding that value;
-- * `trans_last` is the value of the corresponding parameter holding the gloss/translation of the last name
-- (e.g. "trans-last"), and `trans_last_fullname` is the full parameter name holding that value (or nil for
-- no such parameter).
-- * `last_first` if set, when parameters `first` and `last` are used, display the author name as "last, first"
local function add_author(
author,
author_fullname,
trans_author,
trans_author_fullname,
authorlink,
authorlink_fullname,
trans_authorlink,
trans_authorlink_fullname,
first,
first_fullname,
trans_first,
trans_first_fullname,
last,
last_fullname,
trans_last,
trans_last_fullname,
last_first
)
local function make_author_with_url(txt, txtparam, authorlink, authorlink_param)
if authorlink then
if authorlink:find("%[%[") then
error(("Can't specify links in |%s=%s"):format(authorlink_param, authorlink))
end
if txt:find("%[%[") then
error(("Can't specify links in %s=%s"):format(txtparam, txt))
end
return "[[w:" .. authorlink .. "|" .. txt .. "]]"
else
return txt
end
end
local num_authorobjs
if author then
local authorobjs =
parse_multivalued_annotated_text(author, author_fullname, trans_author, trans_author_fullname)
num_authorobjs = #authorobjs
if num_authorobjs == 1 then
if is_anonymous(authorobjs[1].text) then
authorobjs[1].text = "nivîskarê anonîm"
authorobjs[1].link = "nivîskarê anonîm"
end
if authorlink then
authorobjs[1].text = make_author_with_url(
authorobjs[1].text,
"|" .. author_fullname,
authorlink,
"|" .. authorlink_fullname
)
authorobjs[1].link = make_author_with_url(
authorobjs[1].link,
"|" .. author_fullname,
authorlink,
"|" .. authorlink_fullname
)
end
if authorobjs[1].gloss and trans_authorlink then
authorobjs[1].gloss = make_author_with_url(
authorobjs[1].gloss,
("<t:...> in |%s"):format(author_fullname),
trans_authorlink,
"|" .. trans_author_fullname
)
end
add(j_lazim(format_multivalued_annotated_text(authorobjs)))
elseif trans_authorlink then
error(
(
"Can't specify |%s= along with multiple semicolon-separated entities in |%s=; use the "
.. "<t:...> inline modifier attached to the individual entities and put the link directly "
.. "in the value of the inline modifier"
):format(trans_authorlink_fullname, author_fullname)
)
else
-- Allow an authorlink with multiple authors, e.g. for use with |author=Max Mills; Harvey Mills
-- with |authorlink=Max and Harvey. For this we have to generate the entire text and link it
-- all.
local formatted_text = format_multivalued_annotated_text(authorobjs)
if authorlink then
formatted_text = make_author_with_url(
formatted_text,
"|" .. author_fullname,
authorlink,
"|" .. authorlink_fullname
)
end
add(j_lazim(formatted_text))
end
else
num_authorobjs = 1
-- Author separated into first name + last name. We don't currently support non-Latin-script
-- authors separated this way and probably never will.
if first then
if last_first then
author = last .. ", " .. first
else
author = first .. " " .. last
end
else
author = last
end
if authorlink then
local authorparam = first and ("|%s |%s"):format(first_fullname, last_fullname) or "|" .. last_fullname
author = make_author_with_url(author, authorparam, authorlink, authorlink_fullname)
end
local trans_author
if trans_last then
if trans_first then
trans_author = trans_first .. " " .. trans_last
else
trans_author = trans_last
end
if trans_authorlink then
local trans_authorparam = trans_first
and ("|%s |%s"):format(trans_first_fullname, trans_last_fullname)
or "|" .. trans_last_fullname
trans_author = make_author_with_url(
trans_author,
trans_authorparam,
trans_authorlink,
trans_authorlink_fullname
)
end
end
add(j_lazim(author))
if trans_author then
add(SPACE_LBRAC)
add(trans_author)
add(RBRAC)
end
end
author_outputted = true
return num_authorobjs
end
local function add_authorlike(
param,
prefix_with_preceding_authors,
suffix_without_preceding_authors,
suffix_if_multiple,
anonymous_suffix
)
local delimiter = author_outputted and "and" or ", "
local entities, _, num_entities = parse_and_format_multivalued_annotated_text_with_name(param, delimiter)
if not entities then
return
end
if is_anonymous(entities) then
-- If tlr=anonymous or similar given, display as "anonymous translator" or similar. If a specific
-- anonymous suffix not given, try to derive the anonymous suffix from the non-preceding-author suffix.
if not anonymous_suffix then
local cleaned_suffix = suffix_without_preceding_authors
:gsub(" ", " ")
:gsub(" ", " ")
:gsub(" ", " ")
:gsub("[", "[")
:gsub("]", "]")
cleaned_suffix = mw.text.trim(cleaned_suffix)
if not anonymous_suffix then
anonymous_suffix = " " .. cleaned_suffix:match("^, (.*)$")
end
if not anonymous_suffix then
anonymous_suffix = " " .. cleaned_suffix:match("^%((.*)%)$")
end
if not anonymous_suffix then
anonymous_suffix = " " .. cleaned_suffix:match("^%[(.*)%]$")
end
if not anonymous_suffix then
anonymous_suffix = suffix_without_preceding_authors
end
end
add_with_sep(j_lazim("anonîm" .. anonymous_suffix))
else
entities = j_lazim(entities)
if prefix_with_preceding_authors and (author_outputted or not suffix_without_preceding_authors) then
add_with_sep(prefix_with_preceding_authors .. entities)
elseif suffix_if_multiple and num_entities > 1 then
add_with_sep(entities .. suffix_if_multiple)
else
add_with_sep(entities .. suffix_without_preceding_authors)
end
end
author_outputted = true
end
local function add_authorlabel()
local default_authorlabel = a("default-authorlabel")
if default_authorlabel and yesno(a("authorlabel"), true) then
sep = nil
add_with_sep(" " .. default_authorlabel)
end
end
local function has_new_title_or_author()
return args["2ndauthor"]
or args["2ndlast"]
or args.chapter2
or args.title2
or args.tlr2
or args.mainauthor2
or args.editor2
or args.editors2
or args.compiler2
or args.compilers2
or args.director2
or args.directors2
end
local function has_newversion()
return args.newversion or args.location2 or has_new_title_or_author()
end
-- Handle chapter=, section=, etc. `param` is the base name of the parameter in question, e.g. "chapter" or
-- "section". If numeric (either Arabic or Roman), add `numeric_prefix`; otherwise, parse as textual (allowing for
-- language prefixes, inline modifiers, etc.), prefix with `textual_prefix` (if given) and suffix with
-- `textual_suffix` (if given). Also checks for and handles the following (assuming param == "chapter"):
-- * chapterurl=: URL of the chapter.
-- * trans-chapter=: Chapter translation (can be given using an inline modifier <t:...>).
-- * chapter_number=: Chapter number, when chapter= is also given (otherwise put the chapter number in chapter=).
-- * chapter_plain=: Plain version of the chapter number; the "chapter " prefix isn't added.
-- * chapter_series=: Series that the chapter is within (used e.g. for journal articles part of a series).
-- * chapter_seriesvolume=: Volume of the series (compare seriesvolume=).
--
-- Returns nil if no value specified for the main parameter, otherwise the formatted value.
local function format_chapterlike(param, numeric_prefix, textual_prefix, textual_suffix)
local chap, chap_fullname = a_with_name(param)
local chap_num, chap_num_fullname = a_with_name(param .. "_number")
local chap_plain, chap_plain_fullname = parse_and_format_annotated_text_with_name(param .. "_plain")
if chap_num and chap_plain then
error(("Specify only one of |%s= or %s="):format(chap_num_fullname, chap_plain_fullname))
end
local chap_series, chap_series_fullname =
parse_and_format_annotated_text_with_name(param .. "_series", tag_with_cite, tag_with_cite)
local chap_seriesvolume, chap_seriesvolume_fullname =
parse_and_format_annotated_text_with_name(param .. "_seriesvolume")
if chap_series then
chap_series = ", " .. chap_series
end
if chap_seriesvolume then
if not chap_series then
error(("Ji bo |%s= ev %s= lazim e"):format(chap_series_fullname, chap_seriesvolume_fullname))
end
chap_series = chap_series .. " (" .. chap_seriesvolume .. ")"
end
local chapterurl
local function make_chapter_with_url(chap)
if chapterurl then
return "[" .. chapterurl .. " " .. chap .. "]"
else
return chap
end
end
if not chap then
if chap_num then
error(
("Ji bo |%s= ev |%s= lazim e; nirxa hejmarî li |%s= binivîse"):format(
chap_num_fullname,
chap_fullname,
chap_fullname
)
)
end
if chap_plain then
chapterurl = aurl(param .. "url")
return make_chapter_with_url(chap_plain .. (chap_series or ""))
end
return nil
end
local cleaned_chap = chap:gsub("<sup>[^<>]*</sup>", ""):gsub("[*+#]", "")
chapterurl = aurl(param .. "url")
local formatted
if numeric_prefix and get_number(cleaned_chap) then
-- Arabic chapter number
formatted = numeric_prefix .. make_chapter_with_url(chap)
elseif
numeric_prefix
and cleaned_chap:match("^[mdclxviMDCLXVI]+$")
and roman_to_arabic(cleaned_chap, true)
and (not overrides[param] or not overrides[param].noroman)
then
-- Roman chapter number
formatted = numeric_prefix .. make_chapter_with_url(upper(chap))
else
-- strip leading ! (used to force text-mode for titles that look like Roman numerals like "mil" and "dill")
if chap:find("^!") then
chap = chap:gsub("^!", "")
end
-- Must be a chapter name
local chapterobj = parse_annotated_text(chap, chap_fullname, a("trans-" .. param))
chapterobj.text = make_chapter_with_url(chapterobj.text)
chapterobj.link = make_chapter_with_url(chapterobj.link)
formatted = (textual_prefix or "") .. format_annotated_text(chapterobj) .. (textual_suffix or "")
end
if chap_num or chap_plain then
-- NOTE: Up above we throw an error if both chap_num and chap_plain are specified.
formatted = formatted .. " (" .. (chap_plain or numeric_prefix .. chap_num) .. ")"
end
if chap_series then
formatted = formatted .. chap_series
end
return formatted
end
-- This handles everything after displaying the author, starting with the chapter and ending with page, column,
-- line and then other=. It is currently called twice: Once to handle the main portion of the citation, and once to
-- handle a "newversion" citation. `ind` is either "" for the main portion or a number (currently only 2) for a
-- "newversion" citation. In a few places we conditionalize on `ind` to take actions depending on its value.
local function postauthor(ind, num_authors, format_as_cite)
get_full_paramname = make_get_full_paramname(ind)
if author_outputted then
add_authorlabel()
end
local coauthors = parse_and_format_multivalued_annotated_text("coauthors", "and")
coauthors = j_lazim(coauthors)
if coauthors then
local with_prefix = ""
if author_outputted then
with_prefix = "li gel "
if num_authors == 1 then
sep = " "
end
end
add_with_sep(with_prefix .. coauthors)
author_outputted = true
end
add_authorlike("quotee", "quoting ", ", quotee", ", quotees")
if format_as_cite and author_outputted and not date_outputted then
add_date()
sep = ", "
end
add_authorlike("chapter_tlr", "terciman: ", ", tercime", nil, " terciman")
local function add_sg_and_pl_authorlike(noun, verbed)
local sgparam = noun
local plparam = noun .. "s"
local sgval, sgval_fullname = a_with_name(sgparam)
local plval, plval_fullname = a_with_name(plparam)
if sgval and plval then
error(("Nabe ku hem |%s= hem jî |%s= were bikaranîn"):format(sgval_fullname, plval_fullname))
end
if sgval or plval then
local verbed_by = verbed .. " ji hêla "
local comma_sgnoun = ", " .. noun
local comma_plnoun = ", " .. noun .. "s"
add_authorlike(sgparam, verbed_by, comma_sgnoun, comma_plnoun)
add_authorlike(plparam, verbed_by, comma_plnoun)
end
end
local formatted_entry = format_chapterlike("entry", nil, "“", "”")
local formatted_chapter = format_chapterlike("chapter", "chapter ", "“", "”")
local function add_entry()
if formatted_entry then
add_with_sep(formatted_entry)
if not a("notitle") then
add("li ser ")
author_outputted = false
else
author_outputted = true
end
formatted_entry = nil
end
end
local function add_chapter()
add_entry()
if formatted_chapter then
add_with_sep(formatted_chapter)
if not a("notitle") then
add("li ser ")
author_outputted = false
end
formatted_chapter = nil
end
end
local function add_actor_role(format_as_cite)
local role = parse_and_format_multivalued_annotated_text("role", "and")
role = j_lazim(role)
local actor_val, actor_fullname = a_with_name("actor")
local actor_objs = parse_multivalued_annotated_text(actor_val, actor_fullname)
local actor = format_multivalued_annotated_text(actor_objs, "and")
actor = j_lazim(actor)
if format_as_cite then
if role then
if actor then
add_with_sep(actor .. " wekî ")
end
sep = nil
add_with_sep(role)
elseif actor then
add_with_sep(actor .. " (aktor)")
end
else
if role then
add_with_sep("xeberdan ji aliyê " .. role .. " ve")
if actor then
sep = nil
add_with_sep(" (" .. actor .. ")")
end
elseif actor then
add_with_sep(actor .. " (aktor)")
end
end
end
if format_as_cite then
if date_outputted then
add_chapter()
end
local output_len = #output
local mainauthor = parse_and_format_multivalued_annotated_text("mainauthor")
mainauthor = j_lazim(mainauthor)
if mainauthor then
add_with_sep(mainauthor)
end
-- quote-* templates display "jobbed by name" after the author, controlled by the author_outputted flag
author_outputted = false
add_authorlike("tlr", "terciman: ", ", tercime", nil, " terciman")
author_outputted = false
add_sg_and_pl_authorlike("editor", "edîtor")
add_sg_and_pl_authorlike("compiler", "berhevkar")
add_sg_and_pl_authorlike("director", "dîrektor")
add_authorlike("lyricist", nil, " (gotin)", nil, " gotinnivîs")
add_authorlike("lyrics-translator", nil, " (tercime)", nil, " tercimenê gotinan")
add_authorlike("composer", nil, " (muzîk)", nil, " bestekar")
add_actor_role("format_as_cite")
-- if the output length has changed, a credit name has been printed
-- and we can print the date
if output_len ~= #output then
author_outputted = true
add_date()
end
add_chapter()
else
add_chapter()
local mainauthor = parse_and_format_multivalued_annotated_text("mainauthor")
mainauthor = j_lazim(mainauthor)
if mainauthor then
add_with_sep(mainauthor)
author_outputted = true
end
add_authorlike("tlr", "terciman: ", ", tercime", nil, " terciman")
add_sg_and_pl_authorlike("editor", "edîtor")
add_sg_and_pl_authorlike("compiler", "berhevkar")
add_sg_and_pl_authorlike("director", "dîrektor")
add_authorlike("lyricist", nil, " (gotin)", nil, " gotinnivîs")
add_authorlike("lyrics-translator", nil, " (tercime)", nil, " tercimanê gotinan")
add_authorlike("composer", nil, " (muzîk)", nil, " bestekar")
end
local title, title_fullname = a_with_name("title")
local need_comma = false
if title then
local titleobj = parse_annotated_text(title, title_fullname, a("trans-title"))
add(format_annotated_text(titleobj, tag_with_cite, tag_with_cite))
local series = parse_and_format_annotated_text("series")
if series then
add(" (" .. series)
local seriesvolume = parse_and_format_annotated_text("seriesvolume")
if seriesvolume then
add(SEMICOLON_SPACE .. seriesvolume)
end
add(")")
end
need_comma = true
elseif ind == "" then
if not a("notitle") then
add(maintenance_line("Jkx sernavê kitêb an kovarê binivîse"))
need_comma = true
end
end
local archiveurl, archiveurl_fullname = aurl_with_name("archiveurl")
local url, url_fullname = aurl_with_name("url")
local urls, urls_fullname = aurl_with_name("urls")
if url and urls then
error(("Supply only one of |%s= and |%s="):format(url_fullname, urls_fullname))
end
local function verify_title_supplied(url_name)
-- There are too many cases of this to throw an error at this time.
-- if not title then
-- error(("If |%s= is given, |%s= must also be supplied"):format(url_name, title_fullname))
-- end
end
if archiveurl or url then
verify_title_supplied(archiveurl and archiveurl_fullname or url_fullname)
sep = nil
add("‎<sup>[" .. (archiveurl or url) .. "]</sup>")
elseif urls then
verify_title_supplied(urls_fullname)
sep = nil
add("‎<sup>" .. urls .. "</sup>")
add("‎<sup>[https://web.archive.org/" .. urls .. " arşîv]</sup>")
end
-- display (in Language) if language is provided and is not English and not overriden by termlang or worklang
if format_as_cite and ind == "" and not (args.termlang or args.worklang) and (args[1] or args.lang) then
local lang = get_first_lang(args[1] or args.lang)
if lang then
local langcode = lang:getCode()
if not (langcode == "und" or langcode == "en") then
local langs = format_langs(args[1] or args.lang)
if langs then
add(" (in " .. langs .. ")")
end
end
end
end
if need_comma then
sep = ", "
end
local edition, edition_fullname = parse_and_format_annotated_text_with_name("edition")
local edition_plain, edition_plain_fullname = parse_and_format_annotated_text_with_name("edition_plain")
if edition and edition_plain then
error(("Supply only one of |%s= and |%s="):format(edition_fullname, edition_plain_fullname))
end
if edition then
add_with_sep(edition .. " edition")
end
if edition_plain then
add_with_sep(edition_plain)
end
-- Display a numeric param such as page=, volume=, column=. For each `paramname`, four params are actually
-- recognized, e.g. for paramname == "page", the params page=, pages=, page_plain= and pageurl= are recognized
-- and checked (or the same with an index, e.g. page2=, pages2=, page_plain2= and pageurl2= respectively if
-- ind == "2"). Only one of the first three can be specified; an error results if more than one are given.
-- If none are given, the return value is nil; otherwise it is a string. The numeric spec is taken directly
-- from e.g. page_plain= if given; otherwise if e.g. pages= is given, or if page= is given and looks like a
-- combination of numbers (i.e. it has a hyphen or dash in it, a comma, or the word " and "), it is prefixed
-- by `singular_desc` + "s" (e.g. "pages "), otherwise it is prefixed by just `singular_desc` (e.g. "page ").
-- (As a special case, if either e.g. page=unnumbered or pages=unnumbered is given, the numeric spec is
-- "unnumbered page".) The resulting spec is returned directly unless e.g. pageurl= is given, in which case
-- it is linked to the specified URL. Note that any of the specs can be foreign text, e.g. foreign numbers
-- (including with optional inline modifiers), and such text is handled appropriately.
local function format_numeric_param(paramname, singular_desc)
local sgval = a_with_name(paramname)
local sgobj = parse_annotated_text(sgval, paramname)
local plparamname = paramname .. "s"
local plval = a_with_name(plparamname)
local plobj = parse_annotated_text(plval, plparamname)
local plainval, plain_fullname = parse_and_format_annotated_text_with_name(paramname .. "_plain")
local numspec
if not sgval and not plval and not plainval then
return
elseif plainval and (sgval or plval) then
error(("Can't specify " .. plain_fullname .. " with " .. paramname .. " or " .. plparamname))
elseif sgval and plval then
-- if both singular and plural, display "page 1 of 1-10"
numspec = singular_desc .. " " .. sgval .. " of " .. plval
else
-- Merge page= and pages= and treat alike because people often mix them up in both directions.
if plainval then
numspec = plainval
else
local val = sgobj and sgobj.text or plobj.text
if val == "unnumbered" then
numspec = "unnumbered " .. singular_desc
else
local desc
if val:find("^!") then
val = val:gsub("^!", "")
desc = sgval and singular_desc or pluralize(singular_desc)
else
local check_val = val
if check_val:find("%[") then
check_val = remove_links(check_val)
-- convert URL's of the form [URL DISPLAY] to the displayed value
check_val = check_val:gsub("%[[^ %[%]]* ([^%[%]]*)%]", "%1")
end
-- in case of negative page numbers (do they exist?), don't treat as multiple pages
check_val = check_val:gsub("^%-", "")
-- replace HTML entity en-dashes and em-dashes with their literal codes
check_val = check_val:gsub("–", "–")
check_val = check_val:gsub("–", "–")
check_val = check_val:gsub("—", "—")
check_val = check_val:gsub("—", "—")
-- Check for en-dash or em-dash, or two numbers (possibly with stuff after like 12a-15b)
-- separated by a hyphen or by comma a followed by a space (to avoid firing on thousands separators).
if
umatch(check_val, "[–—]")
or check_val:find(" and ")
or check_val:match("%d+[^ ]* *%- *%d+")
or check_val:match("%d+[^ ]* *, +%d+")
then
desc = pluralize(singular_desc)
else
desc = singular_desc
end
end
local obj = sgobj or plobj
obj.text = val
if obj.link:find("^!") then
obj.link = obj.link:gsub("^!", "")
end
val = format_annotated_text(obj)
numspec = desc .. " " .. val
end
end
end
local url = a(paramname .. "url")
if url then
return "[" .. url .. " " .. numspec .. "]"
else
return numspec
end
end
local volume = format_numeric_param("volume", a("volume_prefix") or "volume")
if volume then
add_with_sep(volume)
end
local issue = format_numeric_param("issue", a("issue_prefix") or "number")
if issue then
add_with_sep(issue)
end
-- number= is an alias for issue= (except in {{quote-av}}, where it is the episode number)
local number = format_numeric_param("number", a("number_prefix") or "number")
if number then
add_with_sep(number)
end
local annotations = {}
local genre = a("genre")
if genre then
insert(annotations, genre)
end
local format = a("format")
if format then
insert(annotations, format)
end
local medium = a("medium")
if medium then
insert(annotations, medium)
end
-- Now handle the display of language annotations like "(in French)" or
-- "(quotation in Nauruan; overall work in German)".
local quotelang = args[1] or args.lang
if not quotelang then
if ind == "" then
-- This can only happen for certain non-mainspace pages, e.g. Talk pages; otherwise an error is thrown
-- above.
insert(annotations, maintenance_line("Please specify the language of the quote using |1="))
else
-- do nothing in newversion= portion
end
elseif ind == "" then
local worklang = a("worklang")
local termlang = a("termlang")
worklang = worklang or quotelang
termlang = termlang or quotelang
if worklang == quotelang then
if worklang == termlang then
-- do nothing
else
insert(annotations, "in " .. format_langs(quotelang))
end
else
if quotelang ~= termlang then
insert(annotations, "quotation in " .. format_langs(quotelang))
end
insert(annotations, "overall work in " .. format_langs(worklang))
end
else
local lang2 = a("lang2")
if lang2 then
insert(annotations, "in " .. format_langs(lang2))
end
end
if #annotations > 0 then
sep = nil
add_with_sep(" (" .. concat(annotations, SEMICOLON_SPACE) .. ")")
end
local artist = parse_and_format_multivalued_annotated_text("artist", "and")
artist = j_lazim(artist)
if artist then
add_with_sep("performed by " .. artist)
end
local feat = parse_and_format_multivalued_annotated_text("feat", "and")
feat = j_lazim(feat)
if feat then
sep = " "
add_with_sep("ft. " .. feat)
end
if not format_as_cite then
add_actor_role()
end
local others = parse_and_format_annotated_text("others")
others = j_lazim(others)
if others then
add_with_sep(others)
end
local quoted_in = parse_and_format_annotated_text("quoted_in", tag_with_cite, tag_with_cite)
if quoted_in then
add_with_sep("quoted in " .. quoted_in)
insert(tracking_categories, "Quotations using quoted-in parameter")
end
local location = parse_and_format_multivalued_annotated_text("location", "; ")
local publisher = parse_and_format_multivalued_annotated_text("publisher", "; ")
if publisher then
if location then
add_with_sep(location) -- colon
sep = ": " -- colon
end
add_with_sep(publisher)
elseif location then
add_with_sep(location)
end
if not date_outputted then
add_date("no_paren")
end
local source = parse_and_format_multivalued_annotated_text("source", "and")
if source then
add_with_sep("sourced from " .. source)
end
local original = parse_and_format_annotated_text("original", tag_with_cite, tag_with_cite)
local by = parse_and_format_multivalued_annotated_text("by", "and")
by = j_lazim(by)
local origtype = a("deriv") or "translation"
if original or by then
add_with_sep(origtype .. " of " .. (original or "original") .. (by and " by " .. by or ""))
end
-- Handle origlang=, origworklang=. How we handle them depends on whether the original title or author are explicitly
-- given.
local origlang = a("origlang")
local origworklang = a("origworklang")
local origlangtext, origworklangtext
if origlang then
origlangtext = "in " .. format_langs(origlang)
end
if origworklang then
origworklangtext = "overall work in " .. format_langs(origworklang)
end
if origlang or origworklang then
if original or by then
local orig_annotations = {}
if origlangtext then
insert(orig_annotations, origlangtext)
end
if origworklangtext then
insert(orig_annotations, origworklangtext)
end
sep = nil
add_with_sep(" (" .. concat(orig_annotations, SEMICOLON_SPACE) .. ")")
else
add_with_sep(origtype .. " of original" .. (origlangtext and " " .. origlangtext or ""))
if origworklangtext then
sep = nil
add_with_sep(" (" .. origworklangtext .. ")")
end
end
end
if ind ~= "" and has_newversion() then
local formatted_new_date = format_date_args(a, get_full_paramname, alias_map, "", "", nil, "Please provide a date or year", true)
if formatted_new_date then
add_with_sep(formatted_new_date)
end
end
-- Fetch date_published=/year_published=/month_published= and format appropriately.
local formatted_date_published = format_date_args(a, get_full_paramname, alias_map, "", "_published", nil, nil, true)
local platform = parse_and_format_multivalued_annotated_text("platform", "and")
if formatted_date_published then
add_with_sep("published " .. formatted_date_published .. (platform and " via " .. platform or ""))
elseif platform then
add_with_sep("via " .. platform)
end
-- From here on out, there should always be a preceding item, so we
-- can dispense with add_with_sep() and always insert the comma.
sep = nil
local function small(txt)
add(", <small>")
add(txt)
add("</small>")
end
-- Add an identifier to a book or article database such as DOI, ISBN, JSTOR, etc. `param_or_params`
-- is a string identifying the base param, or a list of such strings to check in turn. If found, the value
-- of the parameter is processed using `process` (a function of one argument, defaulting to mw.uri.encode()),
-- and then the actual URL to insert is generated by preceding with `pretext`, following with `posttext`,
-- and running the resulting string through small(), which first adds a comma and then the URL in small font.
local function add_identifier(param_or_params, pretext, posttext, process)
local val = a(param_or_params)
if val then
val = (process or mw.uri.encode)(val)
small(pretext .. val .. posttext)
end
end
add_identifier("bibcode", "[https://adsabs.harvard.edu/abs/", " →Bibcode]")
add_identifier("doi", '<span class="neverexpand">[https://doi.org/', " →DOI]</span>")
add_identifier("isbn", "", "", isbn)
add_identifier("issn", "", "", issn)
add_identifier("jstor", "[https://www.jstor.org/stable/", " →JSTOR]")
add_identifier("lccn", "", "", lccn)
add_identifier("oclc", "[https://search.worldcat.org/title/", " →OCLC]")
add_identifier("ol", "[https://openlibrary.org/works/OL", "/ →OL]")
add_identifier("pmid", "[https://www.ncbi.nlm.nih.gov/pubmed/", " →PMID]")
add_identifier("pmcid", "[https://www.ncbi.nlm.nih.gov/pmc/articles/", "/ →PMCID]")
add_identifier("ssrn", "[https://ssrn.com/abstract=", " →SSRN]")
-- add_identifier("urn", "", "", urn)
local id = a("id")
if id then
small(id)
end
archiveurl, archiveurl_fullname = aurl_with_name("archiveurl")
if archiveurl then
add(", archived from ")
local url, url_fullname = aurl_with_name("url")
if not url then
-- attempt to infer original URL from archive URL; this works at
-- least for Wayback Machine (web.archive.org) URL's
url = archiveurl:match("/(https?:.*)$")
if not url then
error(
("When |%s= is specified, |%s= must also be included"):format(archiveurl_fullname, url_fullname)
)
end
end
add("[" .. url .. " the original] on ")
local archivedate, archivedate_fullname = a_with_name("archivedate")
if archivedate then
add(format_date(archivedate))
elseif string.sub(archiveurl, 1, 28) == "https://web.archive.org/web/" then
-- If the archive is from the Wayback Machine, then it already contains the date
-- Get the date and format it
local wayback_date = string.sub(archiveurl, 29, 29 + 7)
wayback_date = string.sub(wayback_date, 1, 4)
.. "-"
.. string.sub(wayback_date, 5, 6)
.. "-"
.. string.sub(wayback_date, 7, 8)
add(format_date(wayback_date))
else
error(
("When |%s= is specified, |%s= must also be included"):format(
archiveurl_fullname,
archivedate_fullname
)
)
end
end
if a("accessdate") then
--Otherwise do not display here, as already used as a fallback for missing date= or year= earlier.
if (a("date") or a("nodate") or a("year")) and not a("archivedate") then
add(", retrieved " .. format_date(a("accessdate")))
end
end
local formatted_section = format_chapterlike("section", "section ")
if formatted_section then
add(", ")
add(formatted_section)
end
-- video game stuff
local system = parse_and_format_annotated_text("system")
if system then
add(", " .. system)
end
local scene = parse_and_format_annotated_text("scene")
if scene then
add(", scene: " .. scene)
end
local level = parse_and_format_annotated_text("level")
if level then
add(", level/area: " .. level)
end
local note = parse_and_format_annotated_text("note")
if note then
add(", " .. note)
end
local note_plain = parse_and_format_annotated_text("note_plain")
if note_plain then
add(" " .. note_plain)
end
-- Wrapper around format_numeric_param that inserts the formatted text with optional preceding text.
local function handle_numeric_param(paramname, singular_desc, pretext)
local numspec = format_numeric_param(paramname, singular_desc)
if numspec then
add((pretext or "") .. numspec)
end
end
handle_numeric_param("page", a("page_prefix") or "page", ", ")
handle_numeric_param("column", a("column_prefix") or "column", ", ")
handle_numeric_param("line", a("line_prefix") or "line", ", ")
-- FIXME: Does this make sense? What is other=?
local other = parse_and_format_annotated_text("other")
if other then
add(", " .. other)
end
end
local function add_authors(args, last_first)
-- Find maximum indexed author or last name.
local maxind = math.max(args.author.maxindex, args.last.maxindex)
-- Include max index of ancillary params so we get an error message about their use without the primary params.
local ancillary_params =
{"trans-author", "authorlink", "trans-authorlink", "first", "trans-first", "trans-last"}
for _, ancillary in ipairs(ancillary_params) do
maxind = math.max(maxind, args[ancillary].maxindex)
end
local num_authors = 0
for i = 1, maxind do
local ind = i == 1 and "" or i
local author, last = args.author[i], args.last[i]
if author or last then
local this_num_authors = add_author(
author,
"author" .. ind,
args["trans-author"][i],
"trans-author" .. ind,
args.authorlink[i],
"authorlink" .. ind,
args["trans-authorlink"][i],
"trans-authorlink" .. ind,
args.first[i],
"first" .. ind,
args["trans-first"][i],
"trans-first" .. ind,
last,
"last" .. ind,
args["trans-last"][i],
"trans-last" .. ind,
last_first
)
num_authors = num_authors + this_num_authors
sep = last_first and "; " or ", "
else
for _, cant_have in ipairs(ancillary_params) do
if args[cant_have][i] then
error(("Can't have |%s%s= without |author%s= or |last%s="):format(cant_have, ind, ind, ind))
end
end
end
end
return num_authors
end
local function add_newversion()
-- If there's a "newversion" section, add the new-version text.
if has_newversion() then
sep = nil
--Test for new version of work.
add(SEMICOLON_SPACE)
if args.newversion then -- newversion= is intended for English text, e.g. "quoted in" or "republished as".
add(args.newversion)
elseif not args.edition2 then
if has_new_title_or_author() then
add("republished as")
else
add("republished")
end
end
add(" ")
return ""
else
return ", "
end
end
------------------- Now we start outputting text ----------------------
local need_comma = false
-- Set this now so a() works just below.
get_full_paramname = make_get_full_paramname("")
if args.brackets then
add("[")
end
bold_year = not format_as_cite
year_last = format_as_cite
formatted_date, need_date = format_date_args(
a,
get_full_paramname,
alias_map,
nil,
nil,
bold_year,
"Can we [[:Category:Requests for date|date]] this quote?",
year_last
)
-- format_date_args bolds the year here (via bold_year = not format_as_cite), so this is the only date variable
-- we wrap; origdate=, date_published= and the newversion date are left unwrapped.
formatted_date = j_lazim(formatted_date)
-- Fetch origdate=/origyear=/origmonth= and format appropriately.
formatted_origdate = format_date_args(a, get_full_paramname, alias_map, "orig", nil, nil, nil, year_last)
local num_authors, need_date
if format_as_cite then
num_authors = add_authors(args, "last_first")
if author_outputted then
sep = " "
end
-- Display all the text that comes after the author, for the main portion.
postauthor("", num_authors, "format_as_cite")
author_outputted = false
sep = add_newversion()
-- Add the newversion author(s).
if args["2ndauthor"] or args["2ndlast"] then
num_authors = add_author(
args["2ndauthor"],
"2ndauthor",
nil,
nil,
args["2ndauthorlink"],
"2ndauthorlink",
nil,
nil,
args["2ndfirst"],
"2ndfirst",
nil,
nil,
args["2ndlast"],
"2ndlast",
nil,
nil,
"last_first"
)
sep = ", "
else
for _, cant_have in ipairs{"2ndauthorlink", "2ndfirst"} do
if args[cant_have] then
error(("Can't have |%s= without |2ndauthor= or |2ndlast="):format(cant_have))
end
end
end
-- Display all the text that comes after the author, for the "newversion" section.
postauthor(2, num_authors, "format_as_cite")
else
if formatted_date then
need_comma = true
add(formatted_date)
end
if formatted_origdate then
need_comma = true
add(SPACE_LBRAC .. formatted_origdate .. RBRAC)
end
if need_comma then
sep = ", "
end
date_outputted = true
num_authors = add_authors(args)
-- Display all the text that comes after the author, for the main portion.
postauthor("", num_authors)
author_outputted = false
sep = add_newversion()
-- Add the newversion author(s).
if args["2ndauthor"] or args["2ndlast"] then
num_authors = add_author(
args["2ndauthor"],
"2ndauthor",
nil,
nil,
args["2ndauthorlink"],
"2ndauthorlink",
nil,
nil,
args["2ndfirst"],
"2ndfirst",
nil,
nil,
args["2ndlast"],
"2ndlast",
nil,
nil
)
sep = ", "
else
for _, cant_have in ipairs{"2ndauthorlink", "2ndfirst"} do
if args[cant_have] then
error(("Can't have |%s= without |2ndauthor= or |2ndlast="):format(cant_have))
end
end
end
-- Display all the text that comes after the author, for the "newversion" section.
postauthor(2, num_authors)
end
if not args.nocolon then
sep = nil
add(":")
elseif args.usenodot and not args.nodot then
add(".")
end
-- Concatenate output portions to form output text.
local output_text = concat(output)
-- Remainder of code handles adding categories. We add one or more of the following categories:
--
-- 1. [[Category:LANG terms with quotations]], based on the first language code in termlang= or 1=. Added to
-- mainspace, Reconstruction: and Appendix: pages as well as Citations: pages if the corresponding mainspace
-- page exists. Not added if nocat= is given. Note that [[Module:usex]] adds the same category using the same
-- logic, but we do it here too because we may not have a quotation to format. (We add in those circumstances
-- because typically when there's no quotation to format, it's because it's formatted manually underneath the
-- citation, or using {{ja-x}}, {{th-x}} or similar.)
-- 2. [[Category:Requests for date in LANG entries]], based on the first language code in 1=. Added to mainspace,
-- Reconstruction:, Appendix: and Citations: pages unless nocat= is given.
-- 3. [[Category:Quotations using nocat parameter]], if nocat= is given. Added to mainspace, Reconstruction:,
-- Appendix: and Citations: pages.
local categories = {}
local termlang = get_first_lang(args.termlang or argslang)
if args.nocat then
if format_as_cite then
insert(tracking_categories, "Citations using nocat parameter")
else
insert(tracking_categories, "Quotations using nocat parameter")
end
else
-- don't create language tracking categories for citations
if not format_as_cite then
local title
if args.pagename then -- for testing, doc pages, etc.
title = mw.title.new(args.pagename)
if not title then
error(("Bad value for `args.pagename`: '%s'"):format(args.pagename))
end
else
title = mw.title.getCurrentTitle()
end
-- Only add [[Citations:foo]] to [[:Category:LANG terms with quotations]] if [[foo]] exists.
local ok_to_add_cat
if title.nsText ~= "Citations" then
ok_to_add_cat = true
else
local mainspace_title = mw.title.new(title.text)
if mainspace_title and mainspace_title.exists then
ok_to_add_cat = true
end
end
if ok_to_add_cat then
insert(categories, termlang:getFullName() .. " terms with quotations")
end
end
if need_date then
local argslangobj = get_first_lang(argslang)
insert(categories, "Requests for date in " .. argslangobj:getCanonicalName() .. " entries")
end
end
return output_text .. (
not lang and "" or
(#categories > 0 and format_categories(categories, lang, args.sort) or "") .. (
#tracking_categories > 0
and format_categories(
tracking_categories,
lang,
args.sort,
nil,
not page_should_be_ignored(mw.title.getCurrentTitle().fullText)
)
or ""
)
)
end
-- Alias specs for type= and type2=. Each spec is `{canon, aliases, with_newversion}` where `canon` is the canonical
-- parameter (with "2" added if type2= is being handled), `aliases` is a comma-separated string of aliases (with "2"
-- added if type2= is being handled, except for numeric params), and `with_newversion` indicates whether we should
-- process this spec if type2= is being handled.
local type_alias_specs = {
av = {
{"author", "writer,writers", true},
{"chapter", "episode", true},
{"chapterurl", "episodeurl", true},
{"trans-chapter", "trans-episode", true},
{"chapter_tlr", "episode_tlr,episode_tlrs,episode_translator,episode_translators", true},
{"chapter_series", "episode_series", true},
{"chapter_seriesvolume", "episode_seriesvolume", true},
{"chapter_number", "episode_number", true},
{"chapter_plain", "episode_plain", true},
{"volume", "season", true},
{"volumes", "seasons", true},
{"volume_plain", "season_plain", true},
{"volumeurl", "seasonurl", true},
{"platform", "network", true},
},
book = {
{"author", "3"},
{"editor", "amadekar"},
{"title", ",4"},
{"url", "5"},
{"year", "2"},
{"page", "6"},
{"text", "7"},
{"t", "8"},
},
journal = {
{"year", "2"},
{"author", "3"},
{"chapter", "title,sernav,article,meqale,4", true},
{"chapterurl", "titleurl,articleurl,urlya-sernav,urlya-meqaleyê", true},
{"trans-chapter", "trans-title,trans-article,tercimeya-sernav,tercimeya-meqaleyê", true},
{"chapter_tlr", "title_tlr,title_tlrs,title_translator,title_translators,article_tlr,article_tlrs,article_translator,article_translators,tercimanê-sernav,tercimanên-sernav,tercimanên-meqaleyê,tercimanê-meqaleyê", true},
{"chapter_series", "title_series,article_series,seriya-sernav,seriya-meqaleyê", true},
{"chapter_seriesvolume", "title_seriesvolume,article_seriesvolume,cildê-seriya-sernav,cildê-seriya-meqaleyê", true},
{"chapter_number", "title_number,article_number,hejmara-sernav,hejmara-meqaleyê", true},
{"chapter_plain", "title_plain,article_plain,sernav-sade,meqale-sade", true},
{"title", "journal,kovar,magazine,newspaper,work,xebat,xezete,5", true},
{"trans-title", "trans-journal,tercimeya-kovar,trans-magazine,trans-newspaper,tercimeya-xezete,trans-work,tercimeya-xebat", true},
{"tlr", "journal_tlr,tercimanê-kovarê,journal_tlrs,journal_translator,journal_translators,magazine_tlr,magazine_tlrs,magazine_translator,magazine_translators,newspaper_tlr,newspaper_tlrs,newspaper_translator,tercimanê-xezeteyê,newspaper_translators,work_tlr,tercimanê-xebatê,work_tlrs,work_translator,work_translators", true},
{"url", "6,journalurl,urlya-kovarê,magazineurl,newspaperurl,urlya-xezeteyê,workurl,urlya-xebatê", true},
{"page", "7"},
{"source", "newsagency,ajans,news_agency", true},
{"text", "8"},
{"t", "9"},
},
}
-- Process interally-handled aliases related to type= or type2=. `args` is a table of arguments; `typ` is the value of
-- type= or type2=; newversion=true if we're dealing with type2=; alias_map is used to keep track of alias mappings
-- seen.
local function process_type_aliases(args, typ, newversion, alias_map)
local ind = newversion and "2" or ""
local deprecated = ine(args.lang)
if not type_alias_specs[typ] then
local possible_values = {}
for possible, _ in pairs(type_alias_specs) do
insert(possible_values, possible)
end
sort(possible_values)
error(
("Unrecognized value '%s' for type%s=; possible values are %s"):format(
typ,
ind,
concat(possible_values, ",")
)
)
end
for _, alias_spec in ipairs(type_alias_specs[typ]) do
local canon, aliases, with_newversion = unpack(alias_spec)
if with_newversion or not newversion then
canon = canon .. ind
aliases = split(aliases, ",", true)
local saw_alias = nil
for _, alias in ipairs(aliases) do
if alias:match("^%d+$") then
alias = tonumber(alias)
if deprecated then
alias = alias - 1
end
else
alias = alias .. ind
end
if args[alias] then
if saw_alias == nil then
saw_alias = alias
else
error(("|%s= and |%s= are aliases; cannot specify a value for both"):format(saw_alias, alias))
end
end
end
if saw_alias and (not newversion or type(saw_alias) == "string") then
if args[canon] then
error(("|%s= is an alias of |%s=; cannot specify a value for both"):format(saw_alias, canon))
end
args[canon] = args[saw_alias]
-- Wipe out the original after copying. This is important in case of a param that has general significance
-- but has been redefined (e.g. {{quote-av}} redefines number= for the episode number, and
-- {{quote-journal}} redefines title= for the chapter= (article). It's also important due to unhandled
-- parameter checking.
args[saw_alias] = nil
alias_map[canon] = saw_alias
end
end
end
end
-- Clone and combine frame's and parent's args while also assigning nil to empty strings. Handle aliases and ignores.
local function clone_args(direct_args, parent_args)
local args = {}
-- Processing parent args must come first so that direct args override parent args. Note that if a direct arg is
-- specified but is blank, it will still override the parent arg (with nil).
for pname, param in pairs(parent_args) do
-- [[Special:WhatLinksHere/Wiktionary:Tracking/quote/param/PARAM]]
track("param/" .. pname)
args[pname] = ine(param)
end
-- Process ignores. The value of `ignore` is a comma-separated list of parameter names to ignore (erase). We need to
-- do this before aliases due to {{quote-song}}, which sets chapter= to the value of title= in the direct params and
-- sets title= to the value of album= using an alias. If we do the ignores after aliases, we get an error during alias
-- processing, saying that title= and its alias album= are both present.
local ignores = ine(direct_args.ignore)
if ignores then
for ignore in gsplit(ignores, "%s*,%s*") do
args[ignore] = nil
end
end
local alias_map = {}
local other_controls = {}
-- noroman
local noroman = ine(direct_args.noroman)
if noroman then
other_controls.overrides = other_controls.overrides or {}
for param in gsplit(noroman, "%s*,%s*") do
other_controls.overrides[param] = (other_controls.overrides[param] or {})
other_controls.overrides[param].noroman = true
end
end
-- Process internally-specified aliases using type= or type2=.
local typ = args.type or direct_args.type or args.cure or direct_args.cure
if typ then
process_type_aliases(args, typ, false, alias_map)
end
local typ2 = args.type2 or direct_args.type2 or args.cure2 or direct_args.cure2
if typ2 then
process_type_aliases(args, typ2, true, alias_map)
end
-- Process externally-specified aliases. The value of `alias` is a list of semicolon-separated specs, each of which
-- is of the form DEST:SOURCE,SOURCE,... where DEST is the canonical name of a parameter and SOURCE refers to an
-- alias. Whitespace is allowed between all delimiters. The order of aliases may be important. For example, for
-- {{quote-journal}}, title= contains the article name and is an alias of underlying chapter=, while journal= or
-- work= contains the journal name and is an alias of underlying title=. As a result, the title -> chapter alias
-- must be specified before the journal/work -> title alias.
--
-- Whenever we copy a value from argument SOURCE to argument DEST, we record an entry for the pair in alias_map, so
-- that when we would display an error message about DEST, we display SOURCE instead.
--
-- Do alias processing (and ignore and error_if processing) before processing direct_args so that e.g. we can set up
-- an alias of title -> chapter and then set title= to something else in the direct args ({{quote-hansard}} does
-- this).
--
-- FIXME: Delete this once we've converted all alias processing to internal.
local aliases = ine(direct_args.alias)
if aliases then
-- Allow and discard a trailing semicolon, to make managing multiple aliases easier.
aliases = ugsub(aliases, "%s*;$", "")
for alias_spec in gsplit(aliases, "%s*;%s*") do
local alias_spec_parts = split(alias_spec, "%s*:%s*")
if #alias_spec_parts ~= 2 then
error(("Alias spec '%s' should have one colon in it"):format(alias_spec))
end
local dest, sources = unpack(alias_spec_parts)
sources = split(sources, "%s*,%s*")
local saw_source = nil
for _, source in ipairs(sources) do
if source:match("^%d+$") then
source = tonumber(source)
end
if args[source] then
if saw_source == nil then
saw_source = source
else
error(("|%s= and |%s= are aliases; cannot specify a value for both"):format(saw_source, source))
end
end
end
if saw_source then
if args[dest] then
error(("|%s= is an alias of |%s=; cannot specify a value for both"):format(saw_source, dest))
end
args[dest] = args[saw_source]
-- Wipe out the original after copying. This important in case of a param that has general significance
-- but has been redefined (e.g. {{quote-av}} redefines number= for the episode number, and
-- {{quote-journal}} redefines title= for the chapter= (article). It's also important due to unhandled
-- parameter checking.
args[saw_source] = nil
alias_map[dest] = saw_source
end
end
end
-- Process error_if. The value of `error_if` is a comma-separated list of parameter names to throw an error if seen
-- in parent_args (they are params we overwrite in the direct args).
local error_ifs = ine(direct_args.error_if)
if error_ifs then
for error_if in gsplit(error_ifs, "%s*,%s*") do
if ine(parent_args[error_if]) then
error(
("Cannot specify a value |%s=%s as it would be overwritten or ignored"):format(
error_if,
ine(parent_args[error_if])
)
)
end
end
end
for pname, param in pairs(direct_args) do
-- ignore control params
if pname ~= "ignore" and pname ~= "alias" and pname ~= "error_if" and pname ~= "noroman" then
args[pname] = ine(param)
end
end
return args, alias_map, other_controls
end
local function get_args(frame_args, parent_args, require_lang)
-- FIXME: We are processing arguments twice, once in clone_args() and then again in [[Module:parameters]]. This is
-- wasteful of memory.
local cloned_args, alias_map, other_controls = clone_args(frame_args, parent_args)
local deprecated = ine(parent_args.lang)
local alias_of_t = {alias_of = "t"}
local boolean = {type = "boolean"}
local language_sublist = {type = "language", sublist = true}
local list_allow_holes = {list = true, allow_holes = true}
local script = {type = "script"}
local function alias_list_allow_holes(canon)
return {alias_of = canon, list = true, allow_holes = true}
end
-- First, the "single" params that don't have FOO2 or FOOn versions.
local params = {
[deprecated and "lang" or 1] = {required = require_lang, type = "language", sublist = true, default = "und"},
["lang2"] = language_sublist,
["ziman2"] = {alias_of = "lang2", type = "language", sublist = true},
["newversion"] = true,
["versiyona-nû"] = {alias_of = "newversion"},
["author"] = list_allow_holes,
["nivîskar"] = alias_list_allow_holes("author"),
["2ndauthor"] = true,
["2nivîskar"] = {alias_of = "2ndauthor"},
["trans-author"] = list_allow_holes,
["tercimeya-nivîskar"] = alias_list_allow_holes("trans-author"),
["authorlink"] = list_allow_holes,
["lînka-nivîskar"] = alias_list_allow_holes("authorlink"),
["2ndauthorlink"] = true,
["2lînka-nivîskar"] = {alias_of = "2ndauthorlink"},
["trans-authorlink"] = list_allow_holes,
["lînka-tercimeya-nivîskar"] = alias_list_allow_holes("trans-authorlink"),
["first"] = list_allow_holes,
["pêşnav"] = alias_list_allow_holes("first"),
["2ndfirst"] = true,
["2pêşnav"] = {alias_of = "2ndfirst"},
["trans-first"] = list_allow_holes,
["tercimeya-pêşnav"] = alias_list_allow_holes("trans-first"),
["last"] = list_allow_holes,
["paşnav"] = alias_list_allow_holes("last"),
["2ndlast"] = true,
["2paşnav"] = {alias_of = "2ndlast"},
["trans-last"] = list_allow_holes,
["tercimeya-paşnav"] = alias_list_allow_holes("trans-last"),
["nocat"] = boolean,
["nekat"] = {alias_of = "nocat"},
["nocolon"] = boolean,
-- quote params
text = true,
passage = {alias_of = "text"},
["jêgirtin"] = {alias_of = "text"},
["jg"] = {alias_of = "text"},
tr = true,
transliteration = {alias_of = "tr"},
ts = true,
transcription = {alias_of = "ts"},
norm = true,
normalization = {alias_of = "norm"},
sc = script,
normsc = script,
sort = true,
subst = true,
footer = true,
lit = true,
["hrf"] = {alias_of = "lit"},
t = true,
translation = alias_of_t,
["tercime"] = alias_of_t,
["werger"] = alias_of_t,
["w"] = alias_of_t,
gloss = alias_of_t,
lb = true,
["e"] = {alias_of = "lb"},
brackets = boolean,
-- original quote params
origtext = true,
["metna-orîjînal"] = {alias_of = "origtext"},
origtr = true,
["tr-orîjînal"] = {alias_of = "origtr"},
origts = true,
["ts-orîjînal"] = {alias_of = "origts"},
orignorm = true,
["norm-orîjînal"] = {alias_of = "orignorm"},
origsc = script,
["sc-orîjînal"] = {alias_of = "origsc"},
orignormsc = script,
["normsc-orîjînal"] = {alias_of = "orignormsc"},
origsubst = true,
["subst-orîjînal"] = {alias_of = "origsubst"},
origlb = true,
["e-orîjînal"] = {alias_of = "origlb"},
usenodot = boolean,
nodot = boolean,
inline = boolean,
asquote = boolean,
ascite = boolean,
}
-- Most params have PARAM2 variants for use with `newversion`. FIXME: We should generalize this to
-- allow PARAMN variants for any N.
local function add_with_2(param, value)
params[param] = value
params[param .. "2"] = value
end
local function alias_with_2(alias, canon)
params[alias] = {alias_of = canon}
params[alias .. "2"] = {alias_of = canon .. "2"}
end
for _, param12 in ipairs{
"worklang",
"termlang",
"origlang",
"origworklang"
} do
add_with_2(param12, language_sublist)
end
-- Then the newversion params (which have FOO2 versions).
for _, param12 in ipairs{
-- author-like params; author params themselves are either list params (author=, last=, etc.) or single params
-- (2ndauthor=, 2ndlast=, etc.)
"coauthors",
"quotee",
"tlr",
"editor",
"editors",
"mainauthor",
"compiler",
"compilers",
"director",
"directors",
"lyricist",
"lyrics-translator",
"composer",
"role",
"actor",
"artist",
"feat",
-- author control params
"default-authorlabel",
"authorlabel",
-- title
"title",
"trans-title",
"series",
"seriesvolume",
"notitle",
-- entry
"entry",
"entryurl",
"trans-entry",
-- chapter
"chapter",
"chapterurl",
"chapter_number",
"chapter_plain",
"chapter_series",
"chapter_seriesvolume",
"trans-chapter",
"chapter_tlr",
-- section
"section",
"sectionurl",
"section_number",
"section_plain",
"section_series",
"section_seriesvolume",
"trans-section",
-- other video-game params
"system",
"scene",
"level",
-- URL
"url",
"urls",
"archiveurl",
-- edition
"edition",
"edition_plain",
-- ID params
"bibcode",
"doi",
"isbn",
"issn",
"jstor",
"lccn",
"oclc",
"ol",
"pmid",
"pmcid",
"ssrn",
"urn",
"id",
-- misc date params; most date params handled below
"archivedate",
"accessdate",
"nodate",
-- numeric params handled below
-- other params
"type",
"genre",
"format",
"medium",
"others",
"quoted_in",
"location",
"publisher",
"original",
"by",
"deriv",
"note",
"note_plain",
"other",
"source",
"platform",
} do
add_with_2(param12, true)
end
-- Then the aliases of newversion params (which have FOO2 versions).
for _, param12_aliased in ipairs{
{"role", "roles"},
{"role", "speaker"},
{"tlr", "tlrs"},
{"tlr", "translator"},
{"tlr", "translators"},
{"chapter_tlr", "chapter_tlrs"},
{"chapter_tlr", "chapter_translator"},
{"chapter_tlr", "chapter_translators"},
{"doi", "DOI"},
{"isbn", "ISBN"},
{"issn", "ISSN"},
{"jstor", "JSTOR"},
{"lccn", "LCCN"},
{"oclc", "OCLC"},
{"ol", "OL"},
{"pmid", "PMID"},
{"pmcid", "PMCID"},
{"ssrn", "SSRN"},
{"urn", "URN"},
} do
local canon, alias = unpack(param12_aliased)
alias_with_2(alias, canon)
end
-- tercimeyên Then the aliases of newversion params (which have FOO2 versions).
for _, param12_aliased in ipairs{
{"role", "rol"},
{"role", "axivêr"},
{"tlr", "terciman"},
{"tlr", "wergêr"},
{"chapter_tlr", "tercimanên-beşê"},
{"chapter_tlr", "tercimanê-beşê"},
{"chapter_tlr", "tercimana-beşê"},
{"chapter_tlr", "wergêrên-beşê"},
{"chapter_tlr", "wergêrê-beşê"},
{"chapter_tlr", "wergêra-beşê"},
-- tercimeyên Then the newversion params (which have FOO2 versions).
-- author-like params; author params themselves are either list params (author=, last=, etc.) or single params
-- (2ndauthor=, 2ndlast=, etc.)
{"coauthors", "hevnivîskar"},
{"quotee", "jê-tê-girtin"},
{"editor", "edîtor"},
{"editors", "edîtor"},
{"editor", "amadekar"},
{"mainauthor", "nivîskarê-sereke"},
{"compiler", "berhevkar"},
{"compilers", "berhevkar"},
{"director", "derhêner"},
{"directors", "derhêner"},
{"lyricist", "gotinnivîs"},
{"lyrics-translator", "tercimanê-gotinan"},
{"lyrics-translator", "tercimana-gotinan"},
{"lyrics-translator", "wergêrê-gotinan"},
{"lyrics-translator", "wergêra-gotinan"},
{"composer", "bestekar"},
{"actor", "aktor"},
{"actor", "lîstikvan"},
{"artist", "hunermend"},
{"artist", "artîst"},
{"feat", "mêvan"},
--tercimeyên author control params
{"default-authorlabel", "standard-etîketa-nivîskar"},
{"authorlabel", "etîketa-nivîskar"},
--tercimeyên title
{"title", "sernav"},
{"trans-title", "tercimeya-sernav"},
{"trans-title", "wergera-sernav"},
{"series", "serî"},
{"series", "rêze"},
{"seriesvolume", "cildê-seriyê"},
{"seriesvolume", "cildê-rêzeyê"},
{"notitle", "bêsernav"},
--tercimeyên entry
{"entry", "made"},
{"entryurl", "urlya-madeyê"},
{"trans-entry", "tercimeya-madeyê"},
{"trans-entry", "wergera-madeyê"},
--tercimeyên chapter
{"chapter", "beş"},
{"chapterurl", "urlya-beşê"},
{"chapter_number", "hejmara-beşê"},
{"chapter_plain", "beş-sade"},
{"chapter_series", "seriya-beşê"},
{"chapter_series", "rêzeya-beşê"},
{"chapter_seriesvolume", "cildê-seriya-beşê"},
{"chapter_seriesvolume", "cildê-rêzeya-beşê"},
{"trans-chapter", "tercimeya-beşê"},
{"trans-chapter", "wergera-beşê"},
--tercimeyên section
{"section", "qisim"},
{"section", "qism"},
{"sectionurl", "urlya-qismê"},
{"section_number", "hejmara-qismê"},
{"section_plain", "qisim-sade"},
{"section_plain", "qism-sade"},
{"section_series", "rêzeya-qismê"},
{"section_seriesvolume", "cildê-seriya-qismê"},
{"section_seriesvolume", "cildê-rêzeya-qismê"},
{"trans-section", "tercimeya-qismê"},
--tercimeyên other video-game params
{"system", "sîstem"},
{"system", "pergal"},
{"scene", "sehne"},
--{"level", "level"},
--tercimeyên URL
{"archiveurl", "urlya-arşîvê"},
-- tercimeyên edition
{"edition", "çap"},
{"edition_plain", "çap-sade"},
--tercimeyên misc date params; most date params handled below
{"archivedate", "roja-arşîvê"},
{"archivedate", "tarîxa-arşîvê"},
{"accessdate", "tarîxa-gihiştinê"},
{"accessdate", "roja-gihiştinê"},
{"nodate", "bêtarîx"},
--tercimeyên other params
{"type", "cure"},
{"genre", "janr"},
{"medium", "medya"},
{"others", "kesên-din"},
{"others", "yên-din"},
{"quoted_in", "cihê-jêgirtinê"},
{"location", "cih"},
{"location", "cîh"},
{"publisher", "weşanxane"},
{"publisher", "weşanger"},
{"original", "orîjînal"},
{"original", "orjînal"},
{"by", "ji-aliyê"},
{"by", "ji-hêla"},
--{"deriv", ""},
{"note", "not"},
{"note_plain", "not-sade"},
{"other", "yê-din"},
{"source", "çavkanî"},
--tercimeyên
{"worklang", "zimanê-eserê"},
{"termlang", "zimanê-termê"},
{"origlang", "zimanê-orîjînal"},
{"origworklang", "zimanê-esera-orîjînal"},
} do
local canon, alias = unpack(param12_aliased)
alias_with_2(alias, canon)
end
-- Then the date params.
for _, datelike in ipairs{{"", ""}, {"orig", ""}, {"", "_published"}} do
local pref, suf = unpack(datelike)
for _, arg in ipairs{"date", "year", "month", "start_date", "start_year", "start_month"} do
add_with_2(pref .. arg .. suf, true)
end
end
-- Kurmancî aliases for the date params
for _, date_aliased in ipairs{
{"date", "tarîx"},
{"date", "dîrok"},
{"year", "sal"},
{"month", "meh"},
{"start_date", "tarîxa-destpêkê"},
{"start_year", "sala-destpêkê"},
{"start_month", "meha-destpêkê"},
{"origdate", "tarîxa-orîjînal"},
{"origyear", "sala-orîjînal"},
{"origyear", "sala orîjînal"},
{"origmonth", "meha-orîjînal"},
{"origstart_date", "tarîxa-destpêkê-orîjînal"},
{"origstart_year", "sala-destpêkê-orîjînal"},
{"origstart_month", "meha-destpêkê-orîjînal"},
{"date_published", "tarîxa-weşandinê"},
{"year_published", "sala-weşandinê"},
{"year_published", "sala weşandinê"},
{"month_published", "meha-weşandinê"},
{"start_date_published", "tarîxa-destpêkê-weşandin"},
{"start_year_published", "sala_destpêkê-weşandin"},
{"start_month_published", "meha-destpêkê-weşandin"}
} do
local canon, alias = unpack(date_aliased)
alias_with_2(alias, canon)
end
local numeric_param_suffixes = {"", "s", "_plain", "url", "_prefix"}
-- Then the numeric params.
for _, numeric in ipairs{"volume", "issue", "number", "line", "page", "column"} do
for _, suf in ipairs(numeric_param_suffixes) do
add_with_2(numeric .. suf, true)
end
end
-- canon word -> Kurdish forms
-- aliases : plain synonyms (maps straight to canon, e.g. "volume")
-- sade : ezafe form used before "-sade" (canon .. "_plain")
-- urlya : ezafe form used after "urlya-" (canon .. "url")
local ku_numeric = {
volume = {aliases = {"cild", "cîld"}, sade = "cild", urlya = "cild"},
issue = {aliases = {"hj", "hejmar"}, sade = "hejmar", urlya = "hejmarê"},
number = {aliases = {"hj", "hejmar"}, sade = "hejmar", urlya = "hejmarê"},
line = {aliases = {"rêz"}, sade = "rêz", urlya = "rêzê"},
page = {aliases = {"rûpel", "r"}, sade = "rûpel", urlya = "rûpelê"},
column = {aliases = {"stûn"}, sade = "stûn", urlya = "stûnê"},
}
for canon, t in pairs(ku_numeric) do
for _, alias in ipairs(t.aliases) do
alias_with_2(alias, canon)
end
alias_with_2(t.sade .. "-sade", canon .. "_plain")
alias_with_2("urlya-" .. t.urlya, canon .. "url")
alias_with_2("pêşgira-" .. t.urlya, canon .. "_prefix")
end
-- And the aliases of numeric params.
for _, numeric_aliased in ipairs{{"volume", "vol"}} do
local canon, alias = unpack(numeric_aliased)
for _, suf in ipairs(numeric_param_suffixes) do
alias_with_2(alias .. suf, canon .. suf)
end
end
return process_params(cloned_args, params), alias_map, other_controls
end
local function get_origtext_params(args)
local origtext, origtextlang, origsc, orignormsc
if args.origtext then
-- Wiktionary language codes have at least two lowercase letters followed possibly by lowercase letters and/or
-- hyphens (there are more restrictions but this is close enough). Also check for nonstandard Latin etymology
-- language codes (e.g. VL. or LL.). (There used to be more nonstandard codes but they have all been
-- eliminated.)
origtextlang, origtext = args.origtext:match("^(%l%l[%l-]*):([^ ].*)$")
if not origtextlang then
-- Special hack for Latin variants, which can have nonstandard etym codes, e.g. VL., LL.
origtextlang, origtext = args.origtext:match("^(%uL%.):([^ ].*)$")
end
if not origtextlang then
error("origtext= should begin with a language code prefix")
end
origtextlang = get_lang(origtextlang, nil, "allow etym") or
error("origtext= should begin with a language code prefix")
origsc = args.origsc
orignormsc = args.orignormsc
else
for _, noparam in ipairs{"origtr", "origts", "origsc", "orignorm", "orignormsc", "origsubst", "origlb"} do
if args[noparam] then
error(("Cannot specify %s= without origtext="):format(noparam))
end
end
end
return origtext, origtextlang, origsc, orignormsc
end
local function get_quote(args, is_cite, source)
local text = args.text
local gloss = args.t
local tr = args.tr
local ts = args.ts
local norm = args.norm
local sc = args.sc
local normsc = args.normsc
-- Fetch original-text parameters.
local origtext, origtextlang, origsc, orignormsc = get_origtext_params(args)
-- If any quote-related args are present, display the actual quote; otherwise, display nothing.
if text or gloss or tr or ts or norm or args.origtext then
-- Pass "und" here rather than cause an error; there will be an error on mainspace, Citations, etc. pages
-- in any case in source() if the language is omitted.
local lang = get_first_lang(args[1] or args.lang)
local termlang = args.termlang and get_first_lang(args.termlang) or lang
local usex_data = {
lang = lang,
termlang = termlang,
usex = text,
sc = sc,
translation = gloss,
normalization = norm,
normsc = normsc,
transliteration = tr,
transcription = ts,
brackets = args.brackets,
subst = args.subst,
lit = args.lit,
footer = args.footer,
qq = parse_and_format_labels(args.lb, lang),
quote = "quote-meta",
orig = origtext,
origlang = origtextlang,
origsc = origsc,
orignorm = args.orignorm,
orignormsc = orignormsc,
origtr = args.origtr,
origts = args.origts,
origsubst = args.origsubst,
origqq = parse_and_format_labels(args.origlb, lang),
noreq = args.noreq,
nocat = is_cite or args.nocat,
source = source
}
if args.inline then
-- don't let usex format the footer, otherwise it gets inlined with the rest of the quoted text
usex_data.footer = nil
usex_data.inline = 1
text = format_usex(usex_data)
if text then
text = " “" .. text .. "”"
else
text = ""
end
if args.footer then
text = text .. "<dl><dd>" .. args.footer .. "</dd></dl>"
end
else
text = format_usex(usex_data)
end
elseif args.footer then
text = "<dl><dd>" .. args.footer .. "</dd></dl>"
end
return text
end
-- External interface, meant to be called from a template. Replaces {{quote-meta}} and meant to be the primary
-- interface for {{quote-*}} templates.
function export.quote_t(frame)
local args, alias_map, other_controls = get_args(frame.args, frame:getParent().args, "require_lang")
if args.ascite then
return export.cite_t(frame)
end
return show_quote(args, alias_map, other_controls)
end
function show_quote(args, alias_map, other_controls)
local parts = {}
insert(parts, get_quote(args, false, export.source(args, alias_map, nil, other_controls)))
local retval = concat(parts)
return deprecated and frame:expandTemplate({
title = "check deprecated lang param usage",
args = {retval, lang = args.lang},
}) or retval
end
-- External interface, meant to be called from a template. Replaces {{cite-meta}} and meant to be the primary
-- interface for {{cite-*}} templates.
function export.cite_t(frame)
local parent_args = {}
for k, v in pairs(frame:getParent().args) do
parent_args[k] = v
end
-- use "und" as lang if none provided
if parent_args[1] == nil then
parent_args[1] = "und"
end
local args, alias_map, other_controls = get_args(frame.args, parent_args)
if args.asquote then
return show_quote(args, alias_map, other_controls)
end
return show_cite(args, alias_map, other_controls)
end
function show_cite(args, alias_map, other_controls)
local parts = {}
-- don't nag for translations
if args.text and not args.t then
args.noreq = 1
end
local len_visible = args.text and ulen((args.text:gsub("<[^<>]+>", ""))) or 0
if len_visible == 0 then
if not args.t or args.t == "-" then
args.nocolon = true
end
elseif args.inline == nil then
args.inline = not (
args.block_text
or len_visible > 300
or (args.t and not args.t == "-" and len_visible < 80)
or string.match(args.text, "<br>")
)
end
insert(parts, '<span class="citation-whole"><span class="cited-source">')
insert(parts, export.source(args, alias_map, "format_as_cite", other_controls))
insert(parts, "</span>")
insert(parts, get_quote(args, "is_cite"))
insert(parts, "</span>")
local retval = concat(parts)
return deprecated
and frame:expandTemplate({
title = "check deprecated lang param usage",
args = {retval, lang = args.lang},
})
or retval
end
-- External interface, meant to be called from a template.
function export.call_quote_template(frame)
return export.call_template(frame)
end
-- External interface, for calling a template with overloaded parameters.
function export.overload(frame)
local iargs, overloaded_args = process_params(frame.args, {
[1] = {required = true}
}, true)
local parent_args = frame:getParent().args
for k, v in pairs(parent_args) do
overloaded_args[k] = v
end
return frame:expandTemplate{title = iargs[1], args = overloaded_args}
end
-- External interface, meant to be called from a template.
function export.call_template(frame)
local parameter_sublist = {type = "parameter", sublist = true}
local iargs, other_direct_args = process_params(frame.args, {
["template"] = true,
["textparam"] = parameter_sublist,
["pageparam"] = parameter_sublist,
["propagateparams"] = parameter_sublist,
["allowparams"] = {sublist = true}, -- Doesn't use type = "parameter", because any that end in :list get processed differently.
}, true)
local function fetch_param(source, params)
for _, param in ipairs(params) do
if source[param] then
return source[param]
end
end
return nil
end
local params = {
["text"] = true,
["passage"] = true,
["footer"] = true, -- always propagates, but gets special formatting
}
-- paramaters that can be used during template declaration or included in template calls
-- if the template is called with param=- it will set any declared value to nil
local always_propagate = {
["brackets"] = true,
["usenodot"] = true,
["nodot"] = true,
["ascite"] = true,
["asquote"] = true,
}
for k,v in pairs(always_propagate) do
params[k] = v
end
local textparam = iargs.textparam or {}
for _, param in ipairs(textparam) do
params[param] = true
end
local pageparam = iargs.pageparam or {}
local pageparam1 = pageparam[1]
if pageparam1 ~= nil then
params["page"], params["pages"] = true, true
for _, param in ipairs(pageparam) do
params[param] = true
end
end
local allowparams, allow_all, list = iargs.allowparams, false
if allowparams ~= nil then
for _, allow in ipairs(allowparams) do
local param = allow:match("^(.*):list$")
if param then
if list == nil then
list = {list = true}
end
params[scribunto_parameter_key(param)] = list
elseif allow == "*" then
track("no parameter checking")
allow_all = true
else
params[scribunto_parameter_key(allow)] = true
end
end
else
local parent = frame:getParent()
local template_name = parent:getTitle()
local template_title = new_title(template_name)
local content = template_title:getContent()
local allowed_params, seen = {}, {}
-- Detect all params used by the parent template. param:get_name() takes the
-- parent frame arg table as an argument so that preprocessing will take
-- them into account, since it will matter if the name contains another
-- parameter (e.g. the outer param in "{{{foo{{{bar}}}baz}}}" will change
-- depending on the value for bar=). `seen` memoizes results based on the
-- raw parameter text (which is stored as a string in the parameter object),
-- which avoids unnecessary param:get_name() calls, which are non-trivial.
for param in find_parameters(content) do
local raw = param.raw
if not seen[raw] then
allow = param:get_name(template_args)
params[scribunto_parameter_key(allow)] = true
seen[raw] = true
end
end
end
local propagateparams = iargs.propagateparams or {}
for _, param in ipairs(propagateparams) do
params[param] = true
end
local parent_args = frame:getParent().args
local args = process_params(parent_args, params, allow_all)
parent_args = shallow_copy(parent_args)
if textparam[1] ~= "-" then
other_direct_args.passage = args.text or args.passage or fetch_param(args, textparam)
end
if not (pageparam1 == nil or pageparam1 == "-") then
other_direct_args.page = fetch_param(args, pageparam) or args.page or other_direct_args.page or nil
other_direct_args.pages = args.pages or other_direct_args.pages or nil
if other_direct_args.page == "-" then
other_direct_args.page = nil
end
if other_direct_args.pages == "-" then
other_direct_args.pages = nil
end
end
if args.footer then
other_direct_args.footer = frame:expandTemplate{title = "small", args = {args.footer}}
end
for param, _ in pairs(always_propagate) do
if args[param] == "-" then
other_direct_args[param] = nil
else
other_direct_args[param] = args[param] or other_direct_args[param] or nil
end
end
-- authorlink=- can be used to prevent copying of author= to authorlink= but we don't want to propagate this to
-- the actual {{quote-*}} code.
if other_direct_args.authorlink == "-" then
other_direct_args.authorlink = nil
end
for _, param in ipairs(propagateparams) do
if args[param] then
other_direct_args[param] = args[param]
end
end
return frame:expandTemplate{title = iargs.template or "quote-book", args = other_direct_args}
end
local paramdoc_param_replacements = {
passage = {
param_with_synonym = "<<synonym>>, {{para|text}}, or {{para|passage}}",
param_no_synonym = "{{para|text}} or {{para|passage}}",
text = [=[
* <<params>> – the passage to be quoted.]=],
},
page = {
param_with_synonym = "<<synonym>> or {{para|page}}, or {{para|pages}}",
param_no_synonym = "{{para|page}} or {{para|pages}}",
text = [=[
* <<params>> – '''mandatory in some cases''': the page number(s) quoted from. When quoting a range of pages, note the following:
** Separate the first and last pages of the range with an [[en dash]], like this: {{para|pages|10–11}}.
** You must also use {{para|pageref}} to indicate the page to be linked to (usually the page on which the Wiktionary entry appears).
: This parameter must be specified to have the template link to the online version of the work.]=],
},
page_with_roman_preface = {
param_with_synonym = {"inherit", "page"},
param_no_synonym = {"inherit", "page"},
text = [=[
* <<params>> – '''mandatory in some cases''': the page number(s) quoted from. If quoting from the preface, specify the page number(s) in lowercase Roman numerals. When quoting a range of pages, note the following:
** Separate the first and last page number of the range with an [[en dash]], like this: {{para|pages|10–11}} or {{para|pages|iii–iv}}.
** You must also use {{para|pageref}} to indicate the page to be linked to (usually the page on which the Wiktionary entry appears).
: This parameter must be specified to have the template link to the online version of the work.]=],
},
chapter = {
param_with_synonym = "<<synonym>> or {{para|chapter}}",
param_no_synonym = "{{para|chapter}}",
text = [=[
* <<params>> – the name of the chapter quoted from.]=],
},
roman_chapter = {
param_with_synonym = {"inherit", "chapter"},
param_no_synonym = {"inherit", "chapter"},
text = [=[
* <<params>> – the chapter number quoted from in uppercase Roman numerals.]=],
},
arabic_chapter = {
param_with_synonym = {"inherit", "chapter"},
param_no_synonym = {"inherit", "chapter"},
text = [=[
* <<params>> – the chapter number quoted from in Arabic numerals.]=],
},
trailing_params = {
text = [=[
* {{para|footer}} – a comment on the passage quoted.
* {{para|brackets}} – use {{para|brackets|on}} to surround a quotation with [[bracket#Noun|brackets]]. This indicates that the quotation either contains a mere mention of a term (for example, “some people find the word '''''manoeuvre''''' hard to spell”) rather than an actual use of it (for example, “we need to '''manoeuvre''' carefully to avoid causing upset”), or does not provide an actual instance of a term but provides information about related terms.]=],
},
}
function export.paramdoc(frame)
local parargs = frame:getParent().args
local args = process_params(parargs, {
[1] = true,
})
local text = args[1]
local function do_param_with_optional_synonym(param, text_to_sub, paramtext_synonym, paramtext_no_synonym)
local function sub_param(synonym)
local subbed_paramtext
if synonym then
subbed_paramtext = paramtext_synonym:gsub("<<synonym>>", "{{para|" .. replacement_escape(synonym) .. "}}")
else
subbed_paramtext = paramtext_no_synonym
end
return frame:preprocess((text_to_sub:gsub("<<params>>", replacement_escape(subbed_paramtext))))
end
text = text:gsub("<<" .. pattern_escape(param) .. ">>", function()
return sub_param()
end)
text = text:gsub("<<" .. pattern_escape(param) .. ":(.-)>>", sub_param)
end
local function fetch_text(param_to_replace, key)
local spec = paramdoc_param_replacements[param_to_replace]
local val = spec[key]
if type(val) == "string" then
return val
end
if type(val) == "table" and val[1] == "inherit" then
return fetch_text(val[2], key)
end
error(
"Internal error: Unrecognized value for param '"
.. param_to_replace
.. "', key '"
.. key
.. "': "
.. mw.dumpObject(val)
)
end
for param_to_replace, spec in pairs(paramdoc_param_replacements) do
if not spec.param_no_synonym then
-- Text to substitute directly.
text = text:gsub("<<" .. pattern_escape(param_to_replace) .. ">>", function()
return frame:preprocess(fetch_text(param_to_replace, "text"))
end)
else
do_param_with_optional_synonym(
param_to_replace,
fetch_text(param_to_replace, "text"),
fetch_text(param_to_replace, "param_with_synonym"),
fetch_text(param_to_replace, "param_no_synonym")
)
end
end
-- Remove final newline so template code can add a newline after invocation
text = text:gsub("\n$", "")
return text
end
return export
90ntgrk4oeqnmvrcp71mwuh5x520a5u
6291572
6291566
2026-07-26T15:19:00Z
Wikihez
7344
6291572
Scribunto
text/plain
--[=[
This module contains functions to implement quote-* templates.
Author: Benwing2; conversion into Lua of {{quote-meta/source}} template,
written by Sgconlaw with some help from Erutuon and Benwing2.
The main interface is quote_t(). Note that the source display is handled by source(), which reads both the
arguments passed to it *and* the arguments passed to the parent template, with the former overriding the latter.
]=]
local export = {}
--[=[
FIXME:
1. Dates like `1 march 2025` with the day of the month being 1 and the month lowercased are treated as month-year-only.
The relevant code checks for a capitalized word; it should probably instead check for the actual month names or
abbrevs.
]=]
-- Named constants for all modules used, to make it easier to swap out sandbox versions.
local check_isxn_module = "Module:check isxn"
local debug_track_module = "Module:debug/track"
local en_utilities_module = "Module:en-utilities"
local italics_module = "Module:italics"
local labels_module = "Module:labels"
local languages_module = "Module:languages"
local languages_error_module = "Module:languages/error"
local links_module = "Module:links"
local number_utilities_module = "Module:number utilities"
local parameters_module = "Module:parameters"
local parse_utilities_module = "Module:parse utilities"
local qualifier_module = "Module:qualifier"
local roman_numerals_module = "Module:roman numerals"
local scribunto_module = "Module:Scribunto"
local script_utilities_module = "Module:script utilities"
local scripts_module = "Module:scripts"
local string_pattern_escape_module = "Module:string/patternEscape"
local string_replacement_escape_module = "Module:string/replacementEscape"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local template_parser_module = "Module:template parser"
local usex_module = "Module:usex/ceribandin"
local usex_templates_module = "Module:usex/templates"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"
local concat = table.concat
local insert = table.insert
local new_title = mw.title.new
local remove = table.remove
local require = require
local sort = table.sort
local u = mw.ustring.char
local ugsub = mw.ustring.gsub
local umatch = mw.ustring.match
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
-- Use HTML entities here to avoid parsing issues (esp. with brackets)
local SEMICOLON_SPACE = "; "
local SPACE_LBRAC = " ["
local RBRAC = "]"
local TEMP_LT = u(0xFFF1)
local TEMP_GT = u(0xFFF2)
local TEMP_LBRAC = u(0xFFF3)
local TEMP_RBRAC = u(0xFFF4)
local TEMP_SEMICOLON = u(0xFFF5)
local function apply_subst(...)
apply_subst = require(usex_module).apply_subst
return apply_subst(...)
end
local function check_isbn(...)
check_isbn = require(check_isxn_module).check_isbn
return check_isbn(...)
end
local function check_issn(...)
check_issn = require(check_isxn_module).check_issn
return check_issn(...)
end
local function debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
local function decode_entities(...)
decode_entities = require(string_utilities_module).decode_entities
return decode_entities(...)
end
local function embedded_language_links(...)
embedded_language_links = require(links_module).embedded_language_links
return embedded_language_links(...)
end
local function escape_wikicode(...)
escape_wikicode = require(parse_utilities_module).escape_wikicode
return escape_wikicode(...)
end
local function find_best_script_without_lang(...)
find_best_script_without_lang = require(scripts_module).findBestScriptWithoutLang
return find_best_script_without_lang(...)
end
local function find_parameters(...)
find_parameters = require(template_parser_module).find_parameters
return find_parameters(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function format_processed_labels(...)
format_processed_labels = require(labels_module).format_processed_labels
return format_processed_labels(...)
end
local function format_qualifier(...)
format_qualifier = require(qualifier_module).format_qualifier
return format_qualifier(...)
end
local function format_usex(...)
format_usex = require(usex_module).format_usex
return format_usex(...)
end
local function get_lang(...)
get_lang = require(languages_module).getByCode
return get_lang(...)
end
local function get_number(...)
get_number = require(number_utilities_module).get_number
return get_number(...)
end
local function get_script(...)
get_script = require(scripts_module).getByCode
return get_script(...)
end
local function gsplit(...)
gsplit = require(string_utilities_module).gsplit
return gsplit(...)
end
local function page_should_be_ignored(...)
page_should_be_ignored = require(usex_templates_module).page_should_be_ignored
return page_should_be_ignored(...)
end
local function parse_inline_modifiers(...)
parse_inline_modifiers = require(parse_utilities_module).parse_inline_modifiers
return parse_inline_modifiers(...)
end
local function parse_inline_modifiers_from_segments(...)
parse_inline_modifiers_from_segments = require(parse_utilities_module).parse_inline_modifiers_from_segments
return parse_inline_modifiers_from_segments(...)
end
local function parse_multi_delimiter_balanced_segment_run(...)
parse_multi_delimiter_balanced_segment_run = require(parse_utilities_module).parse_multi_delimiter_balanced_segment_run
return parse_multi_delimiter_balanced_segment_run(...)
end
local function parse_term_with_lang(...)
parse_term_with_lang = require(parse_utilities_module).parse_term_with_lang
return parse_term_with_lang(...)
end
local function pattern_escape(...)
pattern_escape = require(string_pattern_escape_module)
return pattern_escape(...)
end
local function pluralize(...)
pluralize = require(en_utilities_module).pluralize
return pluralize(...)
end
local function process_params(...)
process_params = require(parameters_module).process
return process_params(...)
end
local function remove_links(...)
remove_links = require(links_module).remove_links
return remove_links(...)
end
local function roman_to_arabic(...)
roman_to_arabic = require(roman_numerals_module).roman_to_arabic
return roman_to_arabic(...)
end
local function replacement_escape(...)
replacement_escape = require(string_replacement_escape_module)
return replacement_escape(...)
end
local function scribunto_parameter_key(...)
scribunto_parameter_key = require(scribunto_module).scribunto_parameter_key
return scribunto_parameter_key(...)
end
local function serial_comma_join(...)
serial_comma_join = require(table_module).serialCommaJoin
return serial_comma_join(...)
end
local function shallow_copy(...)
shallow_copy = require(table_module).shallowCopy
return shallow_copy(...)
end
local function split(...)
split = require(string_utilities_module).split
return split(...)
end
local function split_alternating_runs(...)
split_alternating_runs = require(parse_utilities_module).split_alternating_runs
return split_alternating_runs(...)
end
local function split_and_process_raw_labels(...)
split_and_process_raw_labels = require(labels_module).split_and_process_raw_labels
return split_and_process_raw_labels(...)
end
local function split_on_comma(...)
split_on_comma = require(parse_utilities_module).split_on_comma
return split_on_comma(...)
end
local function tag_text(...)
tag_text = require(script_utilities_module).tag_text
return tag_text(...)
end
local function tag_transcription(...)
tag_transcription = require(script_utilities_module).tag_transcription
return tag_transcription(...)
end
local function tag_translit(...)
tag_translit = require(script_utilities_module).tag_translit
return tag_translit(...)
end
local function ulen(...)
ulen = require(string_utilities_module).len
return ulen(...)
end
local function unitalicize_brackets(...)
unitalicize_brackets = require(italics_module).unitalicize_brackets
return unitalicize_brackets(...)
end
local function upper(...)
upper = require(string_utilities_module).upper
return upper(...)
end
local function usub(...)
usub = require(string_utilities_module).sub
return usub(...)
end
local function yesno(...)
yesno = require(yesno_module)
return yesno(...)
end
local function track(page)
debug_track("quote/" .. page)
end
local function maintenance_line(text)
return '<span class="maintenance-line">(' .. text .. ")</span>"
end
local function isbn(text)
return "[[Special:BookSources/"
.. text
.. "|→ISBN]]"
.. check_isbn(
text,
' <span class="error" style="font-size:88%">Invalid ISBN</span>[[Category:Pages with ISBN errors]]'
)
end
local function issn(text)
return "[https://www.worldcat.org/issn/"
.. text
.. " →ISSN]"
.. check_issn(
text,
' <span class="error" style="font-size:88%">Invalid ISSN</span>[[Category:Pages with ISSN errors]]'
)
end
local function lccn(text)
text = text:gsub(" ", "")
if text:find("-") then
-- old-style LCCN; reformat per request by [[User:The Editor's Apprentice]]
local prefix, part1, part2 = text:match("^(.-)(%d+)%-(%d+)$")
if prefix then
if ulen(part2) < 6 then
part2 = ("0"):rep(6 - ulen(part2)) .. part2
end
text = prefix .. part1 .. part2
end
end
return "[https://lccn.loc.gov/" .. mw.uri.encode(text) .. " →LCCN]"
end
local function format_date(text)
return mw.getCurrentFrame():callParserFunction("#formatdate", text, "dmy")
end
-- Parse a raw lb= param (or nil) to individual label info objects and then concatenate them appropriately into a
-- qualifier input, respecting flags like `omit_preComma` and `omit_postSpace` in the label specs.
local function parse_and_format_labels(raw_lb, lang)
if not raw_lb then
return nil
end
local labels = split_and_process_raw_labels{labels = raw_lb, lang = lang, nocat = true}
labels = format_processed_labels{labels = labels, lang = lang, no_ib_content = true}
if labels ~= "" then -- not sure labels can be an empty string but it seems possible in some circumstances
return {labels}
end
end
-- Convert a comma-separated list of language codes to a comma-separated list of language names. `fullname` is the
-- name of the parameter from which the list of language codes was fetched.
local function format_langs(langs)
local names = {}
for i, lang in ipairs(langs) do
names[i] = lang:getCanonicalName()
end
if #names == 1 then
return names[1]
end
return serial_comma_join(names)
end
local function get_first_lang(langs)
return langs[1] or get_lang("und")
end
--[=[
Normally we parse off inline modifiers and language code prefixes in various places, e.g. he:מרים<tr:Miryem>. But we
exclude HTML entries with <span ...>, <i ...>, <br/> or similar in it, caused by wrapping an argument in {{l|...}},
{{lang|...}} or similar. Basically, all tags of the sort we parse here should consist of a less-than sign, plus letters,
plus a colon, e.g. <tr:...>, so if we see a tag on the outer level that isn't in this format, we don't try to parse it.
The restriction to the outer level is to allow generated HTML inside of e.g. qualifier modifiers, such as
foo<q:similar to {{m|fr|bar}}> (if we end up supporting such modifiers).
Also exclude things that look like URL's from being parsed as having language code prefixes.
]=]
local function val_should_not_be_parsed_for_annotations(val)
return val:find("^[^<]*<%l*[^%l:]") or val:find("^%l+://")
end
local param_mods = {
t = {
-- <t:...> and <gloss:...> are aliases.
item_dest = "gloss",
},
gloss = {},
-- Kurmanji aliases of <t:...>/<gloss:...>.
w = {
item_dest = "gloss",
},
werger = {
item_dest = "gloss",
},
tercime = {
item_dest = "gloss",
},
alt = {},
-- Kurmanji alias of <alt:...>.
cuda = {
item_dest = "alt",
},
tr = {},
ts = {},
subst = {},
sc = {type = "script"},
f = {
convert = function(arg, parse_err)
local prefix, val = arg:match("^(.-):([^ ].*)$")
if not prefix then
prefix = ""
val = arg
end
local tags, sc_code, sc = prefix:match("^(.*)/(.-)$")
if sc_code then
sc = get_script(sc_code) or
require(languages_error_module)(sc_code, parse_err, "script code", nil, "not real lang")
else
tags = prefix
end
local quals
if tags ~= "" then
quals = split_on_comma(tags)
for i, qual in ipairs(quals) do
local obj = get_lang(qual, nil, "allow etym") or get_script(qual)
quals[i] = obj or qual
end
end
return {
quals = quals,
sc = sc,
val = val,
}
end,
store = "insert",
},
q = {},
qq = {},
}
local function generate_obj_annotated_text(text, parse_err, paramname)
local obj = {}
if text:find(":[^ ]") or text:find("%[%[") then
local display, is_wikipedia_wikisource
obj.text, obj.lang, obj.link, display, is_wikipedia_wikisource =
parse_term_with_lang {
term = text,
parse_err = parse_err,
paramname = paramname
}
-- HACK: If object is a Wikipedia or Wikisource link, we need to convert it to a two-part link
-- to avoid the w: or s: prefix showing. We should probably avoid this by calling
-- language_link() (in [[Module:links]]) in format_annotated_text() instead of the hacky stuff
-- that we currently do; but we need to test that carefully to make sure it doesn't do things like
-- munge % signs.
if is_wikipedia_wikisource and not obj.text:find("%[%[") and not obj.text:find("%]%]") then
-- Don't directly set .alt, because then if the user specifies <alt:...>, it will cause an error in
-- parse_inline_modifiers().
obj.display = display
end
else
obj.text = text
obj.link = text
end
return obj
end
--[=[
Parse a textual property that may be in a foreign language or script and may be annotated with a language prefix and/or
inline modifiers. `val` is the value of the parameter and `fullname` is the name of the parameter from which the value
was retrieved. `explicit_gloss`, if specified and non-nil, overrides any gloss specified using the <t:...> or
<gloss:...> inline modifier.
If `val` is nil, the return value of this function is nil. Otherwise it is parsed for a language prefix (e.g.
'ar:مُؤَلِّف') and inline modifiers (e.g. 'ar:مُؤَلِّف<t:Author>'), and the return value is an object with the following
fields:
`lang`: The language object corresponding to the language prefix, if specified, or nil if no language prefix is
given.
`text`: The text after stripping off any language prefix and inline modifiers.
`link`: The link part of the text if it consists of a two-part link; otherwise, same as `text`.
`alt`: Display text specified using the <alt:...> modifier, if given; otherwise, nil.
`subst`: Substitutions used to generate the transliteration, in the same format as the subst= parameter.
`sc`: The script object corresponding to the <sc:...> modifier, if given; otherwise nil.
`tr`: The transliteration corresponding to the <tr:...> modifier, if given; otherwise nil.
`ts`: The transcription corresponding to the <ts:...> modifier, if given; otherwise nil.
`gloss`: The gloss/translation corresponding to the `explicit_gloss` parameter (if given and non-nil), otherwise
the <t:...> or <gloss:...> modifiers if given, otherwise nil.
`f`: Foreign versions of the text.
`q`: Left qualifiers.
`qq`: Right qualifiers.
Note that as a special case, if `val` contains HTML tags at the top level (e.g. '<span class="Arab">...</span>', as
might be generated by specifying {{lang|ar|مُؤَلِّف}}), no language prefix or inline modifiers are parsed, and the return
value has the `noscript` field set to true, which tells format_annotated_text() not to try to identify the script of
the text and CSS-tag the text accordingly, but to leave the text untagged.
This object can be passed to format_annotated_text() to format a string displaying the text (appropriately
script-tagged, unless `noscript` is set, as described above) and modifiers.
]=]
local function parse_annotated_text(val, fullname, explicit_gloss)
if not val then
return nil
end
-- When checking for inline modifiers, exclude HTML entry with <span ...>, <i ...>, <br/> or similar in it, caused
-- by wrapping an argument in {{l|...}}, {{lang|...}} or similar. Also exclude URL's from being parsed as having
-- language code prefixes. See val_should_not_be_parsed_for_annotations() for more information. If we find a
-- parameter value with top-level HTML in it, add 'noscript = true' to indicate that we should not try to do script
-- inference and tagging. (Otherwise, e.g. if you specify {{lang|ar|مُؤَلِّف}} as the author, you'll get an extra big
-- font coming from the fact that {{lang|...}} wraps the Arabic text in CSS that increases the size from the
-- default, and then we do script detection and again wrap the text in the same CSS, which increases the size even
-- more.)
if val_should_not_be_parsed_for_annotations(val) then
return {text = val, link = val, noscript = true, gloss = explicit_gloss}
end
local obj
if val:find("<") then
-- Check for inline modifier.
obj = parse_inline_modifiers(val, {
paramname = fullname,
param_mods = param_mods,
generate_obj = generate_obj_annotated_text,
})
else
obj = generate_obj_annotated_text(val, nil, fullname)
end
if explicit_gloss then
obj.gloss = explicit_gloss
end
return obj
end
local html_entity_char_to_replacement = {
["<"] = TEMP_LT,
[">"] = TEMP_GT,
["["] = TEMP_LBRAC,
["]"] = TEMP_RBRAC,
}
local function html_entity_replacement(entity, code_without_semicolon, hash, xcode, x, code)
-- Try to decode the entity. If successful, Replace certain special HTML entities (those that are bracket-like)
-- with single Unicode characters; otherwise, replace the semicolon with a special character so it won't get
-- interpreted as a delimiter.
local ch = decode_entities(entity)
if ch ~= entity then
return html_entity_char_to_replacement[ch] or code_without_semicolon .. TEMP_SEMICOLON
end
-- If the entity doesn't decode, escape it anyway iff it follows a valid format.
if hash == "" then
-- Any nonstandard MediaWiki-only entities have now been filtered off, so treat any non-ASCII characters as
-- invalid.
return xcode:match("^[^\128-\255]+$") and code_without_semicolon .. TEMP_SEMICOLON or entity
elseif x == "" then
return xcode:match("^%d+$") and code_without_semicolon .. TEMP_SEMICOLON or entity
end
return code:match("^%x+$") and code_without_semicolon .. TEMP_SEMICOLON or entity
end
local html_entity_replacement_to_char = {
[TEMP_LT] = "<",
[TEMP_GT] = ">",
[TEMP_LBRAC] = "[",
[TEMP_RBRAC] = "]",
[TEMP_SEMICOLON] = ";",
}
local function undo_html_entity_replacement(txt)
-- Pattern covers everything in html_entity_replacement_to_char.
return (txt:gsub("\239\191[\177-\181]", html_entity_replacement_to_char))
end
-- NOTE: We try hard to optimize this function for the common cases and avoid loading [[Module:parse utilities]]
-- in such cases. The cases we can handle without loading [[Module:parse utilities]] are single values (no
-- semicolons present) without inline modifiers or language prefixes, and multi-entity values (semicolons present)
-- without (a) brackets of any kind (including parens, braces and angle brackets; angle brackets typically indicate
-- inline modifiers and other brackets may protect a semicolon from being interpreted as a delimiter);
-- (b) ampersands (which may indicate HTML entities, which protect a semicolon from being interpreted as a
-- delimiter); and (c) colons not followed by a space (which may indicate a language prefix).
local function generate_obj_multivalued_annotated_text(text, parse_err, paramname, no_undo_html_entity_replacement)
local obj = generate_obj_annotated_text(text, parse_err, paramname)
if not no_undo_html_entity_replacement then
obj.text = undo_html_entity_replacement(obj.text)
obj.link = undo_html_entity_replacement(obj.link)
end
return obj
end
--[=[
Similar to parse_annotated_text() but the parameter value may contain multiple semicolon-separated entities, each with
their own inline modifiers. Some examples:
* mainauthor=Paula Pattengale; Terea Sonsthagen
* author=Katie Brick; J. Cody Nielsen; Greg Jao; Eric Paul Rogers; John A. Monson
* author=Suzanne Brockmann; Patrick G. Lawlor (Patrick Girard); Melanie Ewbank
* author=G Ristori; et al.
* author=Jason Scott; zh:王晰宁<t:Wang Xining>
* editors=zh:包文俊; zh:金心雯
* quotee=zh:張福運<t:Chang Fu-yun>; zh:張景文<t:Chang Ching-wen>
There may be embedded semicolons within brackets, braces or parens that should not be treated as delimiters, e.g.:
* author=Oliver Optic [pseudonym; {{w|William Taylor Adams}}]
* author=author=Shannon Drake (pen name; {{w|Heather Graham Pozzessere}})
* author=James (the Elder;) Humphrys
There may also be HTML entities with semicolons in them:
* author=[{{w|Gilbert Clerke}}]
* 2ndauthor=Martin Biddle & Sally Badham
* author=Peter Christen Asbjørnsen
There may be both embedded semicolons and HTML entities with semicolons in them:
* author=[{{w|Voltaire}} [pseudonym; François-Marie Arouet]]
In general we want to treat [ like an opening bracket and ] like a closing bracket. Beware that they may be
mismatched:
* author=Anonymous [{{w|Karl Maria Kertbeny}}]
Here, `val` is the value of the parameter and `fullname` is the name of the parameter from which the value was
retrieved. `explicit_gloss`, if specified and non-nil, overrides any gloss specified using the <t:...> or <gloss:...>
inline modifier, and `explicit_gloss_fullname` is the name of the parameter from which this value was retrieved. (If
`explicit_gloss` is specified and multiple values were seen, an error results.)
Return value is a list of objects of the same sort as returned by parse_annotated_text().
]=]
local function parse_multivalued_annotated_text(val, fullname, explicit_gloss, explicit_gloss_fullname)
if not val then
return nil
end
-- NOTE: In the code that follows, we use `entity` most of the time to refer to one of the semicolon-separated
-- values in the multivalued param. Entities are most commonly people (typically authors, editors, translators or
-- the like), but may be the names of publishers, locations, or other entities. "Entity" can also refer to HTML
-- entities; in the places where this occurs, the variable name contains 'html' in it.
local splitchar, english_delim
if val:find("^,") then
splitchar = ","
english_delim = "comma"
val = val:gsub("^,", "")
else
splitchar = ";"
english_delim = "semicolon"
end
-- Optimization #1: No semicolons/commas or angle brackets (indicating inline modifiers).
if not val:find("[<" .. splitchar .. "]") then
if val_should_not_be_parsed_for_annotations(val) then
return {{text = val, link = val, noscript = true}}
else
return {generate_obj_multivalued_annotated_text(val, nil, fullname, "no undo html entity replacement")}
end
end
-- Optimization #2: Semicolons/commas but no angle brackets (indicating inline modifiers), braces, brackets, or
-- parens (any of which would protect the semicolon/comma from interpretation as a delimiter), and no ampersand
-- (which might indicate an HTML entity with a terminating semicolon, which should not be interpreted as a
-- delimiter).
if not val:find("[<>%[%](){}&]") then
local entity_objs = {}
for entity in gsplit(val, "%s*" .. splitchar .. "%s*") do
if val_should_not_be_parsed_for_annotations(entity) then
insert(entity_objs, {
text = entity,
link = entity,
noscript = true
})
else
insert(entity_objs, generate_obj_multivalued_annotated_text(entity, nil, fullname, "no undo html entity replacement"))
end
end
return entity_objs
end
-- Escape HTML entities, and get rid of directionality markers.
local amp = val:find("&", nil, true)
if amp then
-- The pattern is more permissive than the usual entity pattern, as MediaWiki has some nonstandard entities
-- that have non-ASCII characters in their codes.
val = val:gsub("((&(#?)(([xX]?)([%w\128-\255]+)));)", html_entity_replacement)
end
-- Pattern covers left-to-right (U+200E) and right-to-left (U+200F).
val = val:gsub("\226\128[\142\143]", "")
-- Parse balanced segment runs, treating HTML entities for left and right bracket and left and right angle bracket
-- as matching literal versions of the same characters.
local entity_runs = parse_multi_delimiter_balanced_segment_run(
val,
{{"[" .. TEMP_LBRAC, "]" .. TEMP_RBRAC}, {"(", ")"}, {"{", "}"}, {"<" .. TEMP_LT, ">" .. TEMP_GT}},
true
)
if type(entity_runs) == "string" then
local undo_val = undo_html_entity_replacement(val)
-- Parse error due to unbalanced delimiters. Don't throw an error here; instead, don't attempt to parse off
-- any annotations, but return the value directly, maybe allowing script tagging (not allowing it if it appears
-- the text is already script-tagged).
return {{text = undo_val, link = undo_val, noscript = not not val_should_not_be_parsed_for_annotations(val)}}
end
-- Split on semicolon (or comma), possibly surrounded by whitespace.
local separated_groups = split_alternating_runs(entity_runs, "%s*" .. splitchar .. "%s*")
-- Process each value.
local entity_objs = {}
for _, entity_group in ipairs(separated_groups) do
-- Rejoin runs that don't involve <...>.
local j = 2
while j <= #entity_group do
if not entity_group[j]:find("^<.*>$") then
entity_group[j - 1] = entity_group[j - 1] .. entity_group[j] .. entity_group[j + 1]
remove(entity_group, j)
remove(entity_group, j)
else
j = j + 2
end
end
local oneval = undo_html_entity_replacement(concat(entity_group))
-- When checking for inline modifiers, exclude HTML entry with <span ...>, <i ...>, <br/> or similar in it,
-- caused by wrapping an argument in {{l|...}}, {{lang|...}} or similar. Also exclude URL's from being parsed
-- as having language code prefixes. This works analogously to parse_annotated_text(); see there for more.
if val_should_not_be_parsed_for_annotations(oneval) then
insert(entity_objs, {
text = oneval,
link = oneval,
noscript = true
})
else
local obj
if #entity_group > 1 then
-- Check for inline modifier.
obj = parse_inline_modifiers_from_segments({
group = entity_group,
arg = oneval,
props = {
paramname = fullname,
param_mods = param_mods,
generate_obj = generate_obj_multivalued_annotated_text,
},
})
else
obj = generate_obj_multivalued_annotated_text(entity_group[1], nil, fullname)
end
insert(entity_objs, obj)
end
end
if explicit_gloss then
if #entity_objs > 1 then
error(
(
"Can't specify |%s= along with multiple %s-separated entities in |%s=; use the <t:...> "
.. "inline modifier attached to the individual entities"
):format(explicit_gloss_fullname, english_delim, fullname)
)
end
entity_objs[1].gloss = explicit_gloss
end
return entity_objs
end
--[=[
Format a text property that may be in a foreign language or script, along with annotations. This is conceptually
similar to the full_link() function in [[Module:links]], but displays the annotations in a different format that is
more appropriate for bibliographic entries. The output looks like this:
TEXT [TRANSLIT /TRANSCRIPTION/, GLOSS]
`textobj` is as returned by parse_annotated_text(). `tag_text_func`, if supplied, is a function of one argument to further
wrap the text after it has been processed and CSS-tagged appropriately, directly before insertion. `tag_gloss_func` is a
similar function for the gloss.
]=]
local function format_annotated_text(textobj, tag_text_func, tag_gloss_func)
if not textobj then
return nil
end
local text, link = textobj.text, textobj.link
local subst, tr, ts, f, gloss = textobj.subst, textobj.tr, textobj.ts, textobj.f, textobj.gloss
-- Retrieve the display text, either specified by the user (.alt) or by parse_annotated_text() (.display) when a
-- Wikipedia link is given.
local alt = textobj.alt or textobj.display
if alt then
if link:find("%[%[") or link:find("%]%]") then
local errmsg = ("Can't currently handle embedded links in '%s', with <alt:...> text '%s'"):format(link, alt)
error(escape_wikicode(errmsg))
end
text = ("[[%s|%s]]"):format(link, alt)
end
-- See above for `noscript`, meaning HTML was found in the text value, probably generated using {{lang|...}}.
-- {{lang}} already script-tags the text and processes embedded language links, so we don't want to do it again (in
-- fact, the code below within the if-clause is similar to what {{lang}} does). In such a case, an explicit language
-- won't be available and find_best_script_without_lang() may not be accurate, so we can't do automatic transliteration.
if not textobj.noscript then
local lang = textobj.lang
-- As an optimization, don't do script detection on an argument that contains only ASCII.
local sc = textobj.sc
or lang and lang:findBestScript(text)
or not text:find("^[ -~]$") and find_best_script_without_lang(text)
or nil
-- As an optimization, don't do any of the following if there's no language, script, translit or transcription,
-- as will be the case with simple ASCII values.
if lang or sc or tr or ts then
if not lang then
lang = get_lang("und")
end
if tr == "-" then
tr = nil
elseif not tr and sc and not sc:getCode():find("Lat") then -- Latn, Latf, Latg, pjt-Latn
-- might return nil
local text_for_tr = text
if subst then
text_for_tr = apply_subst(text_for_tr, subst)
else
text_for_tr = remove_links(text)
end
tr = (lang:transliterate(text_for_tr, sc))
end
if text:find("%[%[") then
-- FIXME: embedded_language_links() replaces % signs with their URL-encoded equivalents,
-- which messes up URL's that may be present (e.g. if chapterurl= is given). IMO this
-- should not happen, and embedded_language_links() should do nothing if no embedded links
-- are present. To work around this, only call embedded_language_links() when there are
-- embedded links present.
text = embedded_language_links({
term = text,
lang = lang,
sc = sc,
})
end
if lang:getCode() ~= "und" or sc:getCode() ~= "Latn" then
text = tag_text(text, lang, sc)
end
if tr then
-- Should we link to the transliteration of languages with lang:link_tr()? Probably not because `text` is not
-- likely to be a term that has an entry.
tr = tag_translit(tr, lang, "usex")
end
if ts then
ts = tag_transcription(ts, lang, "usex")
end
end
end
text = unitalicize_brackets(text)
if tag_text_func then
text = tag_text_func(text)
end
local parts = {}
if textobj.q then
insert(parts, format_qualifier(textobj.q) .. " ")
end
insert(parts, text)
if tr or ts or f or gloss then
insert(parts, SPACE_LBRAC)
local subparts = {}
if tr or ts then
local tr_ts
if ts then
ts = "/" .. ts .. "/"
end
if tr and ts then
tr_ts = tr .. " " .. ts
else
tr_ts = tr or ts
end
insert(subparts, tr_ts)
end
if f then
for _, ff in ipairs(f) do
local sc = ff.sc
local lang
if not sc and ff.quals then
local qual = ff.quals[1]
if type(qual) == "string" then
-- do nothing; we'll do script detection farther down
elseif qual:hasType("script") then
sc = qual
else -- language
sc = qual:findBestScript(ff.val)
lang = qual
end
end
if not lang then
lang = get_lang("und")
end
sc = sc or find_best_script_without_lang(ff.val)
local val = embedded_language_links({
term = ff.val,
lang = lang,
sc = sc,
})
if lang:getCode() ~= "und" or sc:getCode() ~= "Latn" then
val = tag_text(val, lang, sc)
end
local qual_prefix
if ff.quals then
for i, qual in ipairs(ff.quals) do
if type(qual) ~= "string" and (qual:hasType("script") or qual:hasType("language")) then
ff.quals[i] = qual:getCanonicalName()
end
end
qual_prefix = concat(ff.quals, "/") .. ": "
else
qual_prefix = ""
end
insert(subparts, qual_prefix .. val)
end
end
if gloss then
gloss = '<span class="e-translation">' .. gloss .. "</span>"
gloss = unitalicize_brackets(gloss)
if tag_gloss_func then
gloss = tag_gloss_func(gloss)
end
insert(subparts, gloss)
end
insert(parts, concat(subparts, ", "))
insert(parts, RBRAC)
end
if textobj.qq then
insert(parts, " " .. format_qualifier(textobj.qq))
end
return concat(parts)
end
--[=[
Format a multivalued text property that may be in a foreign language or script, along with annotations. This is the
multivalued analog to format_annotated_text(), and formats each individual entity using format_annotated_text(),
joining the results with `delimiter`, which defaults to ", ". It `delimiter` is "and" or "or", join the results using
serial_comma_join() with the specified conjunction.
`textobjs` is as returned by parse_multivalued_annotated_text(). `tag_text_func` and `tag_gloss_func` are as in
format_annotated_text().
]=]
local function format_multivalued_annotated_text(textobjs, delimiter, tag_text_func, tag_gloss_func)
if not textobjs then
return nil
end
if #textobjs == 1 then
return format_annotated_text(textobjs[1], tag_text_func, tag_gloss_func)
end
local parts = {}
for _, textobj in ipairs(textobjs) do
insert(parts, format_annotated_text(textobj, tag_text_func, tag_gloss_func))
end
-- Change delimiter to semicolon if the items themselves contain commas (e.g., in ", Jr.")
local use_semicolon = false
for _, obj in ipairs(textobjs) do
-- Use remove_links to ignore commas in links
if obj.text and remove_links(obj.text):find(", ") then
use_semicolon = true
break
end
end
local n = #parts
if n > 0 and parts[n]:match("^'*et al[.']*$") then
-- Special handling for 'et al.'
parts[n] = "''et al.''"
if n == 2 then
-- author et al.
return concat(parts, " ")
else
-- author 1, author 2, et al.
return concat(parts, (use_semicolon and "; ") or ", ")
end
if delimiter == "and" or delimiter == "or" then
delimiter = ", "
end
return concat(parts, delimiter)
end
if delimiter == "and" or delimiter == "or" then
return serial_comma_join(parts, {conj = delimiter})
end
return concat(parts, delimiter or ((use_semicolon and "; ") or ", "))
end
-- Fancy version of ine() (if-not-empty). Converts empty string to nil, but also strips leading/trailing space.
local function ine(arg)
if not arg then
return nil
elseif type(arg) ~= "string" then
return arg
end
arg = mw.text.trim(arg)
if arg == "" then
return nil
end
return arg
end
local abbrs = {
["a."] = {anchor = "a.", full = "ante"},
["c."] = {anchor = "c.", full = "circa"},
["p."] = {anchor = "p.", full = "post"},
}
-- Process prefixes 'a.' (ante), 'c.' (circa) and 'p.' (post) at the beginning of an arbitrary date or year spec.
-- Returns two values, the formatted version of the prefix and the date spec minus the prefix. If no prefix is found,
-- returns an empty string and the full date.
local function process_ante_circa_post(date)
local prefix = usub(date, 1, 2)
local abbr = abbrs[prefix]
local abbr_prefix = ""
if abbr then
abbr_prefix = "''[[Appendix:Glossary#"
.. abbr.anchor
.. '|<abbr title="'
.. abbr.full
.. '">'
.. abbr.anchor
.. "</abbr>]]'' "
-- Remove lowercase letter, period, and space from beginning of date parameter.
date = ugsub(date, "^%l%.%s*", "")
end
return abbr_prefix, date
end
-- Format the arguments that specify the date of the quotation. These include the following:
-- |date=: The date. If |start_date= is given, this is the end date.
-- |year=, |month=: Year and month of quotation date or end of range, if |date= isn't given.
-- |start_date=: The start date, to specify a range.
-- |start_year=, |start_month=: Year and month of start of range, if |start_date= isn't given.
-- |accessdate=: Date a website was accessed; processed if no other date was given.
-- |nodate=: Indicate that no date is present; otherwise a maintenance line will be displayed if there is no date.
--
-- If `parampref` and/or `paramsuf` are given, this modifies all the date arguments accordingly. For example, if
-- `parampref` == "orig" and `paramsuf` is omitted, the date is specified using |origdate= or |origyear=/|origmonth=,
-- and the start of the range is |origstart_date=, etc. Similarly, if `parampref` is omitted and `paramsuf` is
-- "_published", the date is specified using |date_published= or |year_published=/|month_published=, and the start of
-- the range is |start_date_published=, etc.
--
-- `a` and `get_full_paramname` are functions with the same interpretation as the local functions of the same name in
-- source(). These are used to fetch parameters and get their full names. Note that this may cause all arguments to
-- have an index added to them (|date2=, |year2=, |month2=, etc.).
--
-- `alias_map` is as in source() and is used to map canonical arguments to their aliases when aliases were used.
--
-- If `bold_year` is given, displayed years are boldfaced unless boldface is present in the parameter value.
--
-- If `maintenance_line_no_date` is specified, it should be a string that will be returned if no date is found (i.e.
-- neither |date= nor |year=, or their appropriate equivalents per `parampref` and `paramsuf`, are specified, and
-- neither |nodate= is given to indicate that there is no date, or |accessdate= is given).
--
-- Returns two values: the formatted date and a boolean indicating whether to add a maintenance category
-- [[:Category:Requests for date in LANG entries]]. The first return value will be nil if nothing is to be added
-- (in which case the scond return value will always be nil).
local function format_date_args(
a,
get_full_paramname,
alias_map,
parampref,
paramsuf,
bold_year,
maintenance_line_no_date,
year_last
)
local output = {}
parampref = parampref or ""
paramsuf = paramsuf or ""
local function getp(param)
return a(parampref .. param .. paramsuf)
end
local function pname(param)
local fullname = get_full_paramname(parampref .. param .. paramsuf)
return alias_map[fullname] or fullname
end
-- Format `timestamp` (a timestamp referencing a date) according to the spec in `code`. `param` is the base name of
-- the parameter from which the timestamp was fetched, for error messages.
local function format_date_with_code(code, timestamp, param)
local language = mw.getContentLanguage()
local ok, date = pcall(language.formatDate, language, code, timestamp)
if ok then
return date
else
-- All the formats used in format_date_args() are fine, so the timestamp must be at fault.
error(
(
"Timestamp |%s=%s (possibly canonicalized from its original format) could not be parsed; see the "
.. "[[mw:Help:Extension:ParserFunctions##time|documentation for the #time parser function]]"
):format(pname(param), tostring(timestamp))
)
end
end
-- Try to figure out if the given timestamp has the day of the month explicitly given. We use the following
-- algorithm:
-- 1. Format as year-month-day; if the day is not 1, the day was explicitly given, since if only the year/month are
-- given, the day shows up as 1.
-- 2. If the day shows up as 1 and there isn't a 1 or 01 in the timestamp, the day wasn't explicitly given.
-- 3. Otherwise, if there are three separate numbers (e.g. 2022-07-01), or two separate numbers plus a capitalized
-- letter (taken as an English month, e.g. 2022 July 1), the day was explicitly given, otherwise not.
--
-- `param` is the base name of the parameter from which the timestamp was fetched.
local function date_has_day_specified(timestamp, param)
local day = format_date_with_code("j", timestamp, param)
if day ~= "1" then
return true
end
local english_month = timestamp:find("%u")
local canon_timestamp = mw.text.trim((timestamp:gsub("%D+", " ")))
local seen_nums = split(canon_timestamp, " ", true)
local saw_one = false
for _, num in ipairs(seen_nums) do
if num == "1" or num == "01" then
saw_one = true
break
end
end
if not saw_one then
return false
end
return #seen_nums >= 3 or english_month and #seen_nums >= 2
end
-- Format a date with boldfaced year, as e.g. '''2023''' August 3. `explicit_day_given` indicates whether to include
-- the day; if false, the return value will be e.g. '''2023''' August. `date_param` is the base name of the param
-- from which the date was fetched, for error messages.
local function format_bold_date(date, explicit_day_given, date_param)
local day_month_code = explicit_day_given and "j F" or "F"
local month_day_code = explicit_day_given and "F j" or "F"
if year_last then
if bold_year then
-- This formats like "3 August '''2023'''" (or "August '''2023'''" if day not explicitly given).
return format_date_with_code(day_month_code .. " '''Y'''", date, date_param)
else
-- This formats like "3 August 2023" (or "August 2023" if day not explicitly given).
return format_date_with_code(day_month_code .. " Y", date, date_param)
end
else
if bold_year then
-- This formats like "'''2023''' August 3" (or "'''2023''' August" if day not explicitly given).
return format_date_with_code("'''Y''' " .. month_day_code, date, date_param)
else
-- This formats like "2023 August 3" (or "2023 August" if day not explicitly given).
return format_date_with_code("Y " .. month_day_code, date, date_param)
end
end
end
-- The formatDate method of the mw.language object behaves like the {{#time:}} parser function, which doesn't
-- accept the formats "monthday monthname, year" or "year monthname monthday", but outputs garbage when it receives
-- them, behavior inherited from PHP. {{#formatdate:}} magic word is more forgiving. Fix dates so that, for
-- instance, the |date= parameter of {{quote-journal}} (which uses this code) and the |accessdate= parameter (which
-- uses {{#formatdate:}}) accept similar date formats. See:
-- * [[mw:Extension:Scribunto/Lua_reference_manual#mw.language:formatDate]]
-- * [[mw:Help:Extension:ParserFunctions##time]]
-- * [[mw:Help:Magic_words#Formatting]]
-- `date` is the date spec from the user, which is assumed to come from a parameter whose base name ends in "date";
-- `parampref` is the prefix added to "date" to get the parameter name.
local function fix_date(date, param_pref)
if tonumber(date) ~= nil then
error(
("|%s= should contain a full date (year, month, day of month); use |%s= for year"):format(
pname(param_pref .. "date"),
pname(param_pref .. "year")
)
)
elseif date and date:find("%s*%a+,%s*%d+%s*$") then
error(
("|%s= should contain a full date (year, month, day of month); use |%s=, |%s= for month and year"):format(
pname(param_pref .. "date"),
pname(param_pref .. "month"),
pname(param_pref .. "year")
)
)
end
if date then
-- Commas are replaced with spaces to prevent parsing issues
local fixed_date = ugsub(date, ", *", " ")
return (ugsub(ugsub(fixed_date or date, "(%d+ %a+),", "%1"), "^(%d%d%d%d) (%a+ %d%d?)$", "%2 %1"))
end
end
local start_date, date = fix_date(getp("start_date"), "start_"), fix_date(getp("date"), "")
local year = getp("year")
local month = getp("month")
local start_year = getp("start_year")
local start_month = getp("start_month")
if date and year then
error(("Only one of |%s= or |%s= should be specified"):format(pname("date"), pname("year")))
end
if date and month then
error(
("|%s= should only be specified in conjunction with |%s=, not with |%s="):format(
pname("month"),
pname("year"),
pname("date")
)
)
end
if start_date and start_year then
error(("Only one of |%s= or |%s= should be specified"):format(pname("start_date"), pname("start_year")))
end
if start_date and start_month then
error(
("|%s= should only be specified in conjunction with |%s=, not with |%s="):format(
pname("start_month"),
pname("start_year"),
pname("start_date")
)
)
end
if (start_date or start_year) and not (date or year) then
error(
("|%s= or |%s=/|%s= cannot be specified without specifying |%s= or |%s=/|%s="):format(
pname("start_date"),
pname("start_year"),
pname("start_month"),
pname("date"),
pname("year"),
pname("month")
)
)
end
local dash = " – "
local day_explicitly_given = date and date_has_day_specified(date, "date")
local start_day_explicitly_given = start_date and date_has_day_specified(start_date, "start_date")
-- Format a date with boldfaced year, as e.g. '''2023''' August 3 (if `explicit_day_given` specified) or
-- '''2023''' August (if `explicit_day_given` not specified). If no date specified, fall back to formatting based
-- on the year and (optionally) month params given in `yearobj` and `monthobj`, boldfacing the year if not already.
-- `date_param` is the base name of the param from which the date was fetched, for error messages.
local function format_date_or_year_month(date, yearobj, monthobj, explicit_day_given, date_param)
if date then
return format_bold_date(date, explicit_day_given, date_param)
else
-- Boldface a year spec if it's not already boldface.
if bold_year and not yearobj.text:find("'''") then
-- Clone the year object before modifying it because we may use it later to check against the current
-- year (if we're dealing with start_year).
yearobj = shallow_copy(yearobj)
yearobj.text = "'''" .. yearobj.text .. "'''"
if yearobj.alt then
yearobj.alt = "'''" .. yearobj.alt .. "'''"
end
end
if year_last then
return (monthobj and format_annotated_text(monthobj) .. " " or "") .. format_annotated_text(yearobj)
else
return format_annotated_text(yearobj) .. (monthobj and " " .. format_annotated_text(monthobj) or "")
end
end
end
local yearobj = parse_annotated_text(year, pname("year"))
local monthobj = parse_annotated_text(month, pname("month"))
local start_yearobj = parse_annotated_text(start_year, pname("start_year"))
local start_monthobj = parse_annotated_text(start_month, pname("start_month"))
if start_yearobj then
local abbr_prefix
abbr_prefix, start_yearobj.text = process_ante_circa_post(start_yearobj.text)
start_yearobj.link = select(2, process_ante_circa_post(start_yearobj.link))
insert(output, abbr_prefix)
elseif yearobj then
local abbr_prefix
abbr_prefix, yearobj.text = process_ante_circa_post(yearobj.text)
yearobj.link = select(2, process_ante_circa_post(yearobj.link))
insert(output, abbr_prefix)
end
if start_date or start_year then
local cur_year = yearobj and yearobj.text or format_date_with_code("Y", date, "date")
local cur_month = monthobj and monthobj.text or date and format_date_with_code("F", date, "date") or nil
local cur_day = date and day_explicitly_given and format_date_with_code("j", date, "date") or nil
local beg_year = start_yearobj and start_yearobj.text or format_date_with_code("Y", start_date, "start_date")
local beg_month = start_monthobj and start_monthobj.text
or start_date and format_date_with_code("F", start_date, "start_date")
or nil
local beg_day = start_date
and start_day_explicitly_given
and format_date_with_code("j", start_date, "start_date")
or nil
if year_last then
if beg_day then
insert(output, beg_day .. " " .. beg_month)
else
insert(output, beg_month)
end
if beg_year ~= cur_year then
insert(output, " " .. beg_year)
end
else
insert(output, format_date_or_year_month(
start_date,
start_yearobj,
start_monthobj,
start_day_explicitly_given,
"start_date"
))
end
if cur_year ~= beg_year then
-- Different years; insert current date in full.
if beg_month or cur_month then
insert(output, dash)
else
insert(output, "–")
end
insert(output, format_date_or_year_month(date, yearobj, monthobj, day_explicitly_given, "date"))
elseif cur_month and cur_month ~= beg_month then
local month_ins = monthobj and format_annotated_text(monthobj) or cur_month
-- Same year but different months; insert current month and (if available) current day.
if cur_day then
insert(output, dash)
if year_last then
insert(output, cur_day .. " " .. month_ins)
else
insert(output, month_ins .. " " .. cur_day)
end
else
if beg_day then
insert(output, dash)
else
insert(output, "–")
end
insert(output, month_ins)
end
elseif cur_day and cur_day ~= beg_day then
-- Same year and month but different days; insert current day.
insert(output, "–")
insert(output, cur_day)
else
-- Same year, month and day; or same year and month, and day not available; or same year, and month and
-- day not available. Do nothing. FIXME: Should we throw an error?
end
if year_last and beg_year == cur_year then
if cur_month then
insert(output, " " .. cur_year)
else
insert(output, " " .. cur_year)
end
end
elseif date or yearobj then
insert(output, format_date_or_year_month(date, yearobj, monthobj, day_explicitly_given, "date"))
elseif not maintenance_line_no_date then
-- Not main quote date. Return nil, caller will handle.
return nil, nil
elseif not getp("nodate") then
local accessdate = getp("accessdate")
if accessdate then
local explicit_day_given = date_has_day_specified(accessdate, "accessdate")
insert(output, format_bold_date(accessdate, explicit_day_given, "accessdate") .. " (last accessed)")
else
if mw.title.getCurrentTitle().namespace ~= 10 then
return maintenance_line(maintenance_line_no_date), true
end
return nil, nil
end
end
return ine(concat(output)), nil
end
local function tag_with_cite(txt)
return "<cite>" .. txt .. "</cite>"
end
-- Display the source line of the quote, above the actual quote text. This contains the majority of the logic of this
-- module (formerly contained in {{quote-meta/source}}).
function export.source(args, alias_map, format_as_cite, other_controls)
local tracking_categories = {}
local argslang = args[1] or args.lang
if not argslang then
-- For the moment, only trigger an error on mainspace pages and
-- other pages that are not user pages or pages containing discussions.
-- These are the same pages that appear in the appropriate tracking
-- categories. User and discussion pages have not generally been
-- fixed up to include a language code and so it's more helpful
-- to use a maintenance line than signal an error.
local current_title = mw.title.getCurrentTitle()
if not (current_title.namespace == 10 or page_should_be_ignored(current_title.fullText)) then
require(languages_error_module)(nil, 1)
end
end
-- Given a canonical param, convert it to the original parameter specified by the user (which may have been an
-- alias).
local function alias(param)
return alias_map[param] or param
end
-- Wrap contributor names (authors, editors, translators, compilers, etc.) and the bolded publication year in a
-- span so they can be styled distinctly (e.g. Kurdish Wiktionary's "j-pewist" highlighting).
local function j_lazim(text)
if not text then
return nil
end
return '<span class="j-pewist">' .. text .. "</span>"
end
local output, sep = {}
local overrides = other_controls and other_controls.overrides or {}
-- Add text to the output. The text goes into a list, and we concatenate all the list components together at the
-- end. To make it easier to handle comma-separated items, we keep track (in `sep`) of the separator (if any) that
-- needs to be inserted before the next item added. For example, if we're in the "newversion" code (ind ~= ""), and
-- there's no title and no URL, then the first time we add anything after the title, we don't want to add a
-- separating comma because the preceding text will say "republished " or "republished as " or "translated as " or
-- similar. In all- other cases, we do want to add a separating comma. The bare add() function reset the separator
-- to be nothing, while the add_with_sep() function resets the separator to be the value of `next_sep` (defaulting
-- to ", "), so the next time around we do add a comma to separate `text` from the preceding piece of text.
local function add(text)
if sep then
insert(output, sep)
end
insert(output, text)
sep = nil
end
local function add_with_sep(text, next_sep)
add(text)
sep = next_sep or ", "
end
-- FIXME: This is all very over-engineered, which makes it slow and memory-inefficient.
-- Return a function that generates the actual parameter name associated with a base param (e.g. "author", "last").
-- The actual parameter name may have an index added (an empty string for the first set of params, e.g. author=,
-- last=, or a numeric index for further sets of params, e.g. author2=, last2=, etc.).
local function make_get_full_paramname(ind)
return function(param)
return param .. ind
end
end
-- Function to fetch the actual parameter name associated with a base param (see make_get_full_paramname() above).
-- Assigned at various times below by calling make_get_full_paramname(). We do it this way so that we can have
-- wrapper functions that access params and define them only once.
local get_full_paramname
-- Return two values: the value of a parameter given the base param name (which may have a numeric index added),
-- and the parameter name from which the value was fetched (which may be an alias, i.e. you can't necessarily fetch
-- the parameter value from args[] given this name). The base parameter can be a list of such base params, which
-- are checked in turn, or nil, in which case nil is returned.
local function a_with_name(param)
if not param then
return nil
elseif type(param) ~= "table" then
local fullname = get_full_paramname(param)
return args[fullname], alias(fullname)
end
for _, par in ipairs(param) do
local val, fullname = a_with_name(par)
if val then
return val, alias(fullname)
end
end
return nil
end
-- Fetch the value of a parameter given the base param name (which may have a numeric index added). The base
-- parameter can be a list of such base params, which are checked in turn, or nil, in which case nil is returned.
local function a(param)
return (a_with_name(param))
end
-- Identical to a_with_name(param) except that it verifies that no space is present. Should be used for URL's.
local function aurl_with_name(param)
local value, fullname = a_with_name(param)
if value and value:find(" ") and not value:find("%[") then
error(("URL not allowed to contain a space, but saw |%s=%s"):format(fullname, value))
end
return value, fullname
end
-- Identical to a(param) except that it verifies that no space is present. Should be used for URL's.
local function aurl(param)
return (aurl_with_name(param))
end
-- Convenience function to fetch a parameter that may be in a foreign language or text (and may consequently have
-- a language prefix and/or inline modifiers), parse the annotations and convert the result into a formatted string.
-- This is the same as parse_and_format_annotated_text() below but also returns the full param name as the second
-- return value.
local function parse_and_format_annotated_text_with_name(param, tag_text_func, tag_gloss_func)
local val, fullname = a_with_name(param)
local obj = parse_annotated_text(val, fullname)
return format_annotated_text(obj, tag_text_func, tag_gloss_func), fullname
end
-- Convenience function to fetch a parameter that may be in a foreign language or text (and may consequently have
-- a language prefix and/or inline modifiers), parse the modifiers and convert the result into a formatted string.
-- This is a wrapper around parse_annotated_text() and format_annotated_text(). `param` is the base parameter name (see
-- a_with_name()), `tag_text_func` is an optional function to tag the parameter text after all other processing (e.g.
-- wrap in <cite>...</cite> tags), and `tag_gloss_func` is a similar function for the parameter translation/gloss.
local function parse_and_format_annotated_text(param, tag_text_func, tag_gloss_func)
return (parse_and_format_annotated_text_with_name(param, tag_text_func, tag_gloss_func))
end
-- Convenience function to fetch a multivalued parameter that may be in a foreign language or text (and may
-- consequently have a language prefix and/or inline modifiers), parse the modifiers and convert the result into a
-- formatted string. This is the multivalued analog to parse_and_format_annotated_text_with_name() and returns two
-- values, the formatted string and the full name of the parameter fetched. `delimiter` is as in
-- format_multivalued_annotated_text().
local function parse_and_format_multivalued_annotated_text_with_name(param, delimiter, tag_text_func, tag_gloss_func)
local val, fullname = a_with_name(param)
local objs = parse_multivalued_annotated_text(val, fullname)
local num_objs = objs and #objs or 0
return format_multivalued_annotated_text(objs, delimiter, tag_text_func, tag_gloss_func), fullname, num_objs
end
-- Convenience function to fetch a multivalued parameter that may be in a foreign language or text (and may
-- consequently have a language prefix and/or inline modifiers), parse the modifiers and convert the result into a
-- formatted string. This is the multivalued analog to parse_and_format_annotated_text(). `delimiter` is as in
-- format_multivalued_annotated_text().
local function parse_and_format_multivalued_annotated_text(param, delimiter, tag_text_func, tag_gloss_func)
return (parse_and_format_multivalued_annotated_text_with_name(param, delimiter, tag_text_func, tag_gloss_func))
end
-- This determines whether to display "Mary Bloggs, transl." (if there's no author preceding) or "translated by
-- Mary Bloggs" (if there's an author preceding).
local author_outputted = false
-- When formatting as a citation, the priority is to display a name and a date before the book/chapter title
-- this tracks whether or not the author/date has been displayed
local date_outputted, formatted_date, formatted_origdate = false
local function add_date(no_paren)
if not date_outputted then
if no_paren then
sep = ", "
else
sep = " "
end
if formatted_date then
if no_paren then
add(formatted_date)
else
add("(" .. formatted_date .. ")")
end
end
if formatted_origdate then
add(SPACE_LBRAC .. formatted_origdate .. RBRAC)
end
sep = ", "
date_outputted = true
end
end
local function is_anonymous(val)
return val:match("^[Aa]non[yî]mo?u?s?$") or val:match("^[Aa]non%.?$")
end
-- Add a formatted author (whose values may be specified using `author` or, for compatibility purposes, split
-- among various parameters):
-- * `author` is the value of the author param (e.g. "author", "author2" or "2ndauthor"), and `author_fullname` is
-- the full parameter name holding that value;
-- * `trans_author` is the optional value of the param holding the gloss/translation of the author, and
-- `trans_author_fullname` is the full parameter name holding that value (or nil for no such parameter);
-- * `authorlink` is the value of the authorlink param, which holds the Wikipedia link of the author(s) in `author`,
-- and `authorlink_fullname` is the full parameter name holding that value;
-- * `trans_authorlink` is the optional value of the param holding the Wikipedia link of the gloss/translation of
-- the author, and `trans_authorlink_fullname` is the full parameter name holding that value (or nil for no such
-- parameter);
-- * `first` is the value of the parameter holding the first name of the author, and `first_fullname` is the full
-- parameter name holding that value;
-- * `trans_first` is the value of the corresponding parameter holding the gloss/translation of the first name
-- (e.g. "trans-first"), and `trans_first_fullname` is the full parameter name holding that value (or nil for
-- no such parameter);
-- * `last` is the value of the parameter holding the last name of the author, and `last_fullname` is the full
-- parameter name holding that value;
-- * `trans_last` is the value of the corresponding parameter holding the gloss/translation of the last name
-- (e.g. "trans-last"), and `trans_last_fullname` is the full parameter name holding that value (or nil for
-- no such parameter).
-- * `last_first` if set, when parameters `first` and `last` are used, display the author name as "last, first"
local function add_author(
author,
author_fullname,
trans_author,
trans_author_fullname,
authorlink,
authorlink_fullname,
trans_authorlink,
trans_authorlink_fullname,
first,
first_fullname,
trans_first,
trans_first_fullname,
last,
last_fullname,
trans_last,
trans_last_fullname,
last_first
)
local function make_author_with_url(txt, txtparam, authorlink, authorlink_param)
if authorlink then
if authorlink:find("%[%[") then
error(("Can't specify links in |%s=%s"):format(authorlink_param, authorlink))
end
if txt:find("%[%[") then
error(("Can't specify links in %s=%s"):format(txtparam, txt))
end
return "[[w:" .. authorlink .. "|" .. txt .. "]]"
else
return txt
end
end
local num_authorobjs
if author then
local authorobjs =
parse_multivalued_annotated_text(author, author_fullname, trans_author, trans_author_fullname)
num_authorobjs = #authorobjs
if num_authorobjs == 1 then
if is_anonymous(authorobjs[1].text) then
authorobjs[1].text = "nivîskarê anonîm"
authorobjs[1].link = "nivîskarê anonîm"
end
if authorlink then
authorobjs[1].text = make_author_with_url(
authorobjs[1].text,
"|" .. author_fullname,
authorlink,
"|" .. authorlink_fullname
)
authorobjs[1].link = make_author_with_url(
authorobjs[1].link,
"|" .. author_fullname,
authorlink,
"|" .. authorlink_fullname
)
end
if authorobjs[1].gloss and trans_authorlink then
authorobjs[1].gloss = make_author_with_url(
authorobjs[1].gloss,
("<t:...> in |%s"):format(author_fullname),
trans_authorlink,
"|" .. trans_author_fullname
)
end
add(j_lazim(format_multivalued_annotated_text(authorobjs)))
elseif trans_authorlink then
error(
(
"Can't specify |%s= along with multiple semicolon-separated entities in |%s=; use the "
.. "<t:...> inline modifier attached to the individual entities and put the link directly "
.. "in the value of the inline modifier"
):format(trans_authorlink_fullname, author_fullname)
)
else
-- Allow an authorlink with multiple authors, e.g. for use with |author=Max Mills; Harvey Mills
-- with |authorlink=Max and Harvey. For this we have to generate the entire text and link it
-- all.
local formatted_text = format_multivalued_annotated_text(authorobjs)
if authorlink then
formatted_text = make_author_with_url(
formatted_text,
"|" .. author_fullname,
authorlink,
"|" .. authorlink_fullname
)
end
add(j_lazim(formatted_text))
end
else
num_authorobjs = 1
-- Author separated into first name + last name. We don't currently support non-Latin-script
-- authors separated this way and probably never will.
if first then
if last_first then
author = last .. ", " .. first
else
author = first .. " " .. last
end
else
author = last
end
if authorlink then
local authorparam = first and ("|%s |%s"):format(first_fullname, last_fullname) or "|" .. last_fullname
author = make_author_with_url(author, authorparam, authorlink, authorlink_fullname)
end
local trans_author
if trans_last then
if trans_first then
trans_author = trans_first .. " " .. trans_last
else
trans_author = trans_last
end
if trans_authorlink then
local trans_authorparam = trans_first
and ("|%s |%s"):format(trans_first_fullname, trans_last_fullname)
or "|" .. trans_last_fullname
trans_author = make_author_with_url(
trans_author,
trans_authorparam,
trans_authorlink,
trans_authorlink_fullname
)
end
end
add(j_lazim(author))
if trans_author then
add(SPACE_LBRAC)
add(trans_author)
add(RBRAC)
end
end
author_outputted = true
return num_authorobjs
end
local function add_authorlike(
param,
prefix_with_preceding_authors,
suffix_without_preceding_authors,
suffix_if_multiple,
anonymous_suffix
)
local delimiter = author_outputted and "and" or ", "
local entities, _, num_entities = parse_and_format_multivalued_annotated_text_with_name(param, delimiter)
if not entities then
return
end
if is_anonymous(entities) then
-- If tlr=anonymous or similar given, display as "anonymous translator" or similar. If a specific
-- anonymous suffix not given, try to derive the anonymous suffix from the non-preceding-author suffix.
if not anonymous_suffix then
local cleaned_suffix = suffix_without_preceding_authors
:gsub(" ", " ")
:gsub(" ", " ")
:gsub(" ", " ")
:gsub("[", "[")
:gsub("]", "]")
cleaned_suffix = mw.text.trim(cleaned_suffix)
if not anonymous_suffix then
anonymous_suffix = " " .. cleaned_suffix:match("^, (.*)$")
end
if not anonymous_suffix then
anonymous_suffix = " " .. cleaned_suffix:match("^%((.*)%)$")
end
if not anonymous_suffix then
anonymous_suffix = " " .. cleaned_suffix:match("^%[(.*)%]$")
end
if not anonymous_suffix then
anonymous_suffix = suffix_without_preceding_authors
end
end
add_with_sep(j_lazim("anonîm" .. anonymous_suffix))
else
entities = j_lazim(entities)
if prefix_with_preceding_authors and (author_outputted or not suffix_without_preceding_authors) then
add_with_sep(prefix_with_preceding_authors .. entities)
elseif suffix_if_multiple and num_entities > 1 then
add_with_sep(entities .. suffix_if_multiple)
else
add_with_sep(entities .. suffix_without_preceding_authors)
end
end
author_outputted = true
end
local function add_authorlabel()
local default_authorlabel = a("default-authorlabel")
if default_authorlabel and yesno(a("authorlabel"), true) then
sep = nil
add_with_sep(" " .. default_authorlabel)
end
end
local function has_new_title_or_author()
return args["2ndauthor"]
or args["2ndlast"]
or args.chapter2
or args.title2
or args.tlr2
or args.mainauthor2
or args.editor2
or args.editors2
or args.compiler2
or args.compilers2
or args.director2
or args.directors2
end
local function has_newversion()
return args.newversion or args.location2 or has_new_title_or_author()
end
-- Handle chapter=, section=, etc. `param` is the base name of the parameter in question, e.g. "chapter" or
-- "section". If numeric (either Arabic or Roman), add `numeric_prefix`; otherwise, parse as textual (allowing for
-- language prefixes, inline modifiers, etc.), prefix with `textual_prefix` (if given) and suffix with
-- `textual_suffix` (if given). Also checks for and handles the following (assuming param == "chapter"):
-- * chapterurl=: URL of the chapter.
-- * trans-chapter=: Chapter translation (can be given using an inline modifier <t:...>).
-- * chapter_number=: Chapter number, when chapter= is also given (otherwise put the chapter number in chapter=).
-- * chapter_plain=: Plain version of the chapter number; the "chapter " prefix isn't added.
-- * chapter_series=: Series that the chapter is within (used e.g. for journal articles part of a series).
-- * chapter_seriesvolume=: Volume of the series (compare seriesvolume=).
--
-- Returns nil if no value specified for the main parameter, otherwise the formatted value.
local function format_chapterlike(param, numeric_prefix, textual_prefix, textual_suffix)
local chap, chap_fullname = a_with_name(param)
local chap_num, chap_num_fullname = a_with_name(param .. "_number")
local chap_plain, chap_plain_fullname = parse_and_format_annotated_text_with_name(param .. "_plain")
if chap_num and chap_plain then
error(("Specify only one of |%s= or %s="):format(chap_num_fullname, chap_plain_fullname))
end
local chap_series, chap_series_fullname =
parse_and_format_annotated_text_with_name(param .. "_series", tag_with_cite, tag_with_cite)
local chap_seriesvolume, chap_seriesvolume_fullname =
parse_and_format_annotated_text_with_name(param .. "_seriesvolume")
if chap_series then
chap_series = ", " .. chap_series
end
if chap_seriesvolume then
if not chap_series then
error(("Ji bo |%s= ev %s= lazim e"):format(chap_series_fullname, chap_seriesvolume_fullname))
end
chap_series = chap_series .. " (" .. chap_seriesvolume .. ")"
end
local chapterurl
local function make_chapter_with_url(chap)
if chapterurl then
return "[" .. chapterurl .. " " .. chap .. "]"
else
return chap
end
end
if not chap then
if chap_num then
error(
("Ji bo |%s= ev |%s= lazim e; nirxa hejmarî li |%s= binivîse"):format(
chap_num_fullname,
chap_fullname,
chap_fullname
)
)
end
if chap_plain then
chapterurl = aurl(param .. "url")
return make_chapter_with_url(chap_plain .. (chap_series or ""))
end
return nil
end
local cleaned_chap = chap:gsub("<sup>[^<>]*</sup>", ""):gsub("[*+#]", "")
chapterurl = aurl(param .. "url")
local formatted
if numeric_prefix and get_number(cleaned_chap) then
-- Arabic chapter number
formatted = numeric_prefix .. make_chapter_with_url(chap)
elseif
numeric_prefix
and cleaned_chap:match("^[mdclxviMDCLXVI]+$")
and roman_to_arabic(cleaned_chap, true)
and (not overrides[param] or not overrides[param].noroman)
then
-- Roman chapter number
formatted = numeric_prefix .. make_chapter_with_url(upper(chap))
else
-- strip leading ! (used to force text-mode for titles that look like Roman numerals like "mil" and "dill")
if chap:find("^!") then
chap = chap:gsub("^!", "")
end
-- Must be a chapter name
local chapterobj = parse_annotated_text(chap, chap_fullname, a("trans-" .. param))
chapterobj.text = make_chapter_with_url(chapterobj.text)
chapterobj.link = make_chapter_with_url(chapterobj.link)
formatted = (textual_prefix or "") .. format_annotated_text(chapterobj) .. (textual_suffix or "")
end
if chap_num or chap_plain then
-- NOTE: Up above we throw an error if both chap_num and chap_plain are specified.
formatted = formatted .. " (" .. (chap_plain or numeric_prefix .. chap_num) .. ")"
end
if chap_series then
formatted = formatted .. chap_series
end
return formatted
end
-- This handles everything after displaying the author, starting with the chapter and ending with page, column,
-- line and then other=. It is currently called twice: Once to handle the main portion of the citation, and once to
-- handle a "newversion" citation. `ind` is either "" for the main portion or a number (currently only 2) for a
-- "newversion" citation. In a few places we conditionalize on `ind` to take actions depending on its value.
local function postauthor(ind, num_authors, format_as_cite)
get_full_paramname = make_get_full_paramname(ind)
if author_outputted then
add_authorlabel()
end
local coauthors = parse_and_format_multivalued_annotated_text("coauthors", "and")
coauthors = j_lazim(coauthors)
if coauthors then
local with_prefix = ""
if author_outputted then
with_prefix = "li gel "
if num_authors == 1 then
sep = " "
end
end
add_with_sep(with_prefix .. coauthors)
author_outputted = true
end
add_authorlike("quotee", "quoting ", ", quotee", ", quotees")
if format_as_cite and author_outputted and not date_outputted then
add_date()
sep = ", "
end
add_authorlike("chapter_tlr", "terciman: ", ", tercime", nil, " terciman")
local function add_sg_and_pl_authorlike(noun, verbed)
local sgparam = noun
local plparam = noun .. "s"
local sgval, sgval_fullname = a_with_name(sgparam)
local plval, plval_fullname = a_with_name(plparam)
if sgval and plval then
error(("Nabe ku hem |%s= hem jî |%s= were bikaranîn"):format(sgval_fullname, plval_fullname))
end
if sgval or plval then
local verbed_by = verbed .. " ji hêla "
local comma_sgnoun = ", " .. noun
local comma_plnoun = ", " .. noun .. "s"
add_authorlike(sgparam, verbed_by, comma_sgnoun, comma_plnoun)
add_authorlike(plparam, verbed_by, comma_plnoun)
end
end
local formatted_entry = format_chapterlike("entry", nil, "“", "”")
local formatted_chapter = format_chapterlike("chapter", "chapter ", "“", "”")
local function add_entry()
if formatted_entry then
add_with_sep(formatted_entry)
if not a("notitle") then
add("li ser ")
author_outputted = false
else
author_outputted = true
end
formatted_entry = nil
end
end
local function add_chapter()
add_entry()
if formatted_chapter then
add_with_sep(formatted_chapter)
if not a("notitle") then
add("li ser ")
author_outputted = false
end
formatted_chapter = nil
end
end
local function add_actor_role(format_as_cite)
local role = parse_and_format_multivalued_annotated_text("role", "and")
role = j_lazim(role)
local actor_val, actor_fullname = a_with_name("actor")
local actor_objs = parse_multivalued_annotated_text(actor_val, actor_fullname)
local actor = format_multivalued_annotated_text(actor_objs, "and")
actor = j_lazim(actor)
if format_as_cite then
if role then
if actor then
add_with_sep(actor .. " wekî ")
end
sep = nil
add_with_sep(role)
elseif actor then
add_with_sep(actor .. " (aktor)")
end
else
if role then
add_with_sep("xeberdan ji aliyê " .. role .. " ve")
if actor then
sep = nil
add_with_sep(" (" .. actor .. ")")
end
elseif actor then
add_with_sep(actor .. " (aktor)")
end
end
end
if format_as_cite then
if date_outputted then
add_chapter()
end
local output_len = #output
local mainauthor = parse_and_format_multivalued_annotated_text("mainauthor")
mainauthor = j_lazim(mainauthor)
if mainauthor then
add_with_sep(mainauthor)
end
-- quote-* templates display "jobbed by name" after the author, controlled by the author_outputted flag
author_outputted = false
add_authorlike("tlr", "terciman: ", ", tercime", nil, " terciman")
author_outputted = false
add_sg_and_pl_authorlike("editor", "edîtor")
add_sg_and_pl_authorlike("compiler", "berhevkar")
add_sg_and_pl_authorlike("director", "dîrektor")
add_authorlike("lyricist", nil, " (gotin)", nil, " gotinnivîs")
add_authorlike("lyrics-translator", nil, " (tercime)", nil, " tercimenê gotinan")
add_authorlike("composer", nil, " (muzîk)", nil, " bestekar")
add_actor_role("format_as_cite")
-- if the output length has changed, a credit name has been printed
-- and we can print the date
if output_len ~= #output then
author_outputted = true
add_date()
end
add_chapter()
else
add_chapter()
local mainauthor = parse_and_format_multivalued_annotated_text("mainauthor")
mainauthor = j_lazim(mainauthor)
if mainauthor then
add_with_sep(mainauthor)
author_outputted = true
end
add_authorlike("tlr", "terciman: ", ", tercime", nil, " terciman")
add_sg_and_pl_authorlike("editor", "edîtor")
add_sg_and_pl_authorlike("compiler", "berhevkar")
add_sg_and_pl_authorlike("director", "dîrektor")
add_authorlike("lyricist", nil, " (gotin)", nil, " gotinnivîs")
add_authorlike("lyrics-translator", nil, " (tercime)", nil, " tercimanê gotinan")
add_authorlike("composer", nil, " (muzîk)", nil, " bestekar")
end
local title, title_fullname = a_with_name("title")
local need_comma = false
if title then
local titleobj = parse_annotated_text(title, title_fullname, a("trans-title"))
add(format_annotated_text(titleobj, tag_with_cite, tag_with_cite))
local series = parse_and_format_annotated_text("series")
if series then
add(" (" .. series)
local seriesvolume = parse_and_format_annotated_text("seriesvolume")
if seriesvolume then
add(SEMICOLON_SPACE .. seriesvolume)
end
add(")")
end
need_comma = true
elseif ind == "" then
if not a("notitle") then
add(maintenance_line("Jkx sernavê kitêb an kovarê binivîse"))
need_comma = true
end
end
local archiveurl, archiveurl_fullname = aurl_with_name("archiveurl")
local url, url_fullname = aurl_with_name("url")
local urls, urls_fullname = aurl_with_name("urls")
if url and urls then
error(("Supply only one of |%s= and |%s="):format(url_fullname, urls_fullname))
end
local function verify_title_supplied(url_name)
-- There are too many cases of this to throw an error at this time.
-- if not title then
-- error(("If |%s= is given, |%s= must also be supplied"):format(url_name, title_fullname))
-- end
end
if archiveurl or url then
verify_title_supplied(archiveurl and archiveurl_fullname or url_fullname)
sep = nil
add("‎<sup>[" .. (archiveurl or url) .. "]</sup>")
elseif urls then
verify_title_supplied(urls_fullname)
sep = nil
add("‎<sup>" .. urls .. "</sup>")
end
-- display (in Language) if language is provided and is not English and not overriden by termlang or worklang
if format_as_cite and ind == "" and not (args.termlang or args.worklang) and (args[1] or args.lang) then
local lang = get_first_lang(args[1] or args.lang)
if lang then
local langcode = lang:getCode()
if not (langcode == "und" or langcode == "ku") then
local langs = format_langs(args[1] or args.lang)
if langs then
add(" (bi " .. langs .. ")")
end
end
end
end
if need_comma then
sep = ", "
end
local edition, edition_fullname = parse_and_format_annotated_text_with_name("edition")
local edition_plain, edition_plain_fullname = parse_and_format_annotated_text_with_name("edition_plain")
if edition and edition_plain then
error(("Supply only one of |%s= and |%s="):format(edition_fullname, edition_plain_fullname))
end
if edition then
add_with_sep(edition .. " edition")
end
if edition_plain then
add_with_sep(edition_plain)
end
-- Display a numeric param such as page=, volume=, column=. For each `paramname`, four params are actually
-- recognized, e.g. for paramname == "page", the params page=, pages=, page_plain= and pageurl= are recognized
-- and checked (or the same with an index, e.g. page2=, pages2=, page_plain2= and pageurl2= respectively if
-- ind == "2"). Only one of the first three can be specified; an error results if more than one are given.
-- If none are given, the return value is nil; otherwise it is a string. The numeric spec is taken directly
-- from e.g. page_plain= if given; otherwise if e.g. pages= is given, or if page= is given and looks like a
-- combination of numbers (i.e. it has a hyphen or dash in it, a comma, or the word " and "), it is prefixed
-- by `singular_desc` + "s" (e.g. "pages "), otherwise it is prefixed by just `singular_desc` (e.g. "page ").
-- (As a special case, if either e.g. page=unnumbered or pages=unnumbered is given, the numeric spec is
-- "unnumbered page".) The resulting spec is returned directly unless e.g. pageurl= is given, in which case
-- it is linked to the specified URL. Note that any of the specs can be foreign text, e.g. foreign numbers
-- (including with optional inline modifiers), and such text is handled appropriately.
local function format_numeric_param(paramname, singular_desc)
local sgval = a_with_name(paramname)
local sgobj = parse_annotated_text(sgval, paramname)
local plparamname = paramname .. "s"
local plval = a_with_name(plparamname)
local plobj = parse_annotated_text(plval, plparamname)
local plainval, plain_fullname = parse_and_format_annotated_text_with_name(paramname .. "_plain")
local numspec
if not sgval and not plval and not plainval then
return
elseif plainval and (sgval or plval) then
error(("Can't specify " .. plain_fullname .. " with " .. paramname .. " or " .. plparamname))
elseif sgval and plval then
-- if both singular and plural, display "page 1 of 1-10"
numspec = singular_desc .. " " .. sgval .. " of " .. plval
else
-- Merge page= and pages= and treat alike because people often mix them up in both directions.
if plainval then
numspec = plainval
else
local val = sgobj and sgobj.text or plobj.text
if val == "unnumbered" then
numspec = "unnumbered " .. singular_desc
else
local desc
if val:find("^!") then
val = val:gsub("^!", "")
desc = sgval and singular_desc or pluralize(singular_desc)
else
local check_val = val
if check_val:find("%[") then
check_val = remove_links(check_val)
-- convert URL's of the form [URL DISPLAY] to the displayed value
check_val = check_val:gsub("%[[^ %[%]]* ([^%[%]]*)%]", "%1")
end
-- in case of negative page numbers (do they exist?), don't treat as multiple pages
check_val = check_val:gsub("^%-", "")
-- replace HTML entity en-dashes and em-dashes with their literal codes
check_val = check_val:gsub("–", "–")
check_val = check_val:gsub("–", "–")
check_val = check_val:gsub("—", "—")
check_val = check_val:gsub("—", "—")
-- Check for en-dash or em-dash, or two numbers (possibly with stuff after like 12a-15b)
-- separated by a hyphen or by comma a followed by a space (to avoid firing on thousands separators).
if
umatch(check_val, "[–—]")
or check_val:find(" and ")
or check_val:match("%d+[^ ]* *%- *%d+")
or check_val:match("%d+[^ ]* *, +%d+")
then
desc = pluralize(singular_desc)
else
desc = singular_desc
end
end
local obj = sgobj or plobj
obj.text = val
if obj.link:find("^!") then
obj.link = obj.link:gsub("^!", "")
end
val = format_annotated_text(obj)
numspec = desc .. " " .. val
end
end
end
local url = a(paramname .. "url")
if url then
return "[" .. url .. " " .. numspec .. "]"
else
return numspec
end
end
local volume = format_numeric_param("volume", a("volume_prefix") or "volume")
if volume then
add_with_sep(volume)
end
local issue = format_numeric_param("issue", a("issue_prefix") or "number")
if issue then
add_with_sep(issue)
end
-- number= is an alias for issue= (except in {{quote-av}}, where it is the episode number)
local number = format_numeric_param("number", a("number_prefix") or "number")
if number then
add_with_sep(number)
end
local annotations = {}
local genre = a("genre")
if genre then
insert(annotations, genre)
end
local format = a("format")
if format then
insert(annotations, format)
end
local medium = a("medium")
if medium then
insert(annotations, medium)
end
-- Now handle the display of language annotations like "(in French)" or
-- "(quotation in Nauruan; overall work in German)".
local quotelang = args[1] or args.lang
if not quotelang then
if ind == "" then
-- This can only happen for certain non-mainspace pages, e.g. Talk pages; otherwise an error is thrown
-- above.
insert(annotations, maintenance_line("Please specify the language of the quote using |1="))
else
-- do nothing in newversion= portion
end
elseif ind == "" then
local worklang = a("worklang")
local termlang = a("termlang")
worklang = worklang or quotelang
termlang = termlang or quotelang
if worklang == quotelang then
if worklang == termlang then
-- do nothing
else
insert(annotations, "in " .. format_langs(quotelang))
end
else
if quotelang ~= termlang then
insert(annotations, "quotation in " .. format_langs(quotelang))
end
insert(annotations, "overall work in " .. format_langs(worklang))
end
else
local lang2 = a("lang2")
if lang2 then
insert(annotations, "in " .. format_langs(lang2))
end
end
if #annotations > 0 then
sep = nil
add_with_sep(" (" .. concat(annotations, SEMICOLON_SPACE) .. ")")
end
local artist = parse_and_format_multivalued_annotated_text("artist", "and")
artist = j_lazim(artist)
if artist then
add_with_sep("performed by " .. artist)
end
local feat = parse_and_format_multivalued_annotated_text("feat", "and")
feat = j_lazim(feat)
if feat then
sep = " "
add_with_sep("ft. " .. feat)
end
if not format_as_cite then
add_actor_role()
end
local others = parse_and_format_annotated_text("others")
others = j_lazim(others)
if others then
add_with_sep(others)
end
local quoted_in = parse_and_format_annotated_text("quoted_in", tag_with_cite, tag_with_cite)
if quoted_in then
add_with_sep("quoted in " .. quoted_in)
insert(tracking_categories, "Quotations using quoted-in parameter")
end
local location = parse_and_format_multivalued_annotated_text("location", "; ")
local publisher = parse_and_format_multivalued_annotated_text("publisher", "; ")
if publisher then
if location then
add_with_sep(location) -- colon
sep = ": " -- colon
end
add_with_sep(publisher)
elseif location then
add_with_sep(location)
end
if not date_outputted then
add_date("no_paren")
end
local source = parse_and_format_multivalued_annotated_text("source", "and")
if source then
add_with_sep("sourced from " .. source)
end
local original = parse_and_format_annotated_text("original", tag_with_cite, tag_with_cite)
local by = parse_and_format_multivalued_annotated_text("by", "and")
by = j_lazim(by)
local origtype = a("deriv") or "translation"
if original or by then
add_with_sep(origtype .. " of " .. (original or "original") .. (by and " by " .. by or ""))
end
-- Handle origlang=, origworklang=. How we handle them depends on whether the original title or author are explicitly
-- given.
local origlang = a("origlang")
local origworklang = a("origworklang")
local origlangtext, origworklangtext
if origlang then
origlangtext = "in " .. format_langs(origlang)
end
if origworklang then
origworklangtext = "overall work in " .. format_langs(origworklang)
end
if origlang or origworklang then
if original or by then
local orig_annotations = {}
if origlangtext then
insert(orig_annotations, origlangtext)
end
if origworklangtext then
insert(orig_annotations, origworklangtext)
end
sep = nil
add_with_sep(" (" .. concat(orig_annotations, SEMICOLON_SPACE) .. ")")
else
add_with_sep(origtype .. " of original" .. (origlangtext and " " .. origlangtext or ""))
if origworklangtext then
sep = nil
add_with_sep(" (" .. origworklangtext .. ")")
end
end
end
if ind ~= "" and has_newversion() then
local formatted_new_date = format_date_args(a, get_full_paramname, alias_map, "", "", nil, "Please provide a date or year", true)
if formatted_new_date then
add_with_sep(formatted_new_date)
end
end
-- Fetch date_published=/year_published=/month_published= and format appropriately.
local formatted_date_published = format_date_args(a, get_full_paramname, alias_map, "", "_published", nil, nil, true)
local platform = parse_and_format_multivalued_annotated_text("platform", "and")
if formatted_date_published then
add_with_sep("published " .. formatted_date_published .. (platform and " via " .. platform or ""))
elseif platform then
add_with_sep("via " .. platform)
end
-- From here on out, there should always be a preceding item, so we
-- can dispense with add_with_sep() and always insert the comma.
sep = nil
local function small(txt)
add(", <small>")
add(txt)
add("</small>")
end
-- Add an identifier to a book or article database such as DOI, ISBN, JSTOR, etc. `param_or_params`
-- is a string identifying the base param, or a list of such strings to check in turn. If found, the value
-- of the parameter is processed using `process` (a function of one argument, defaulting to mw.uri.encode()),
-- and then the actual URL to insert is generated by preceding with `pretext`, following with `posttext`,
-- and running the resulting string through small(), which first adds a comma and then the URL in small font.
local function add_identifier(param_or_params, pretext, posttext, process)
local val = a(param_or_params)
if val then
val = (process or mw.uri.encode)(val)
small(pretext .. val .. posttext)
end
end
add_identifier("bibcode", "[https://adsabs.harvard.edu/abs/", " →Bibcode]")
add_identifier("doi", '<span class="neverexpand">[https://doi.org/', " →DOI]</span>")
add_identifier("isbn", "", "", isbn)
add_identifier("issn", "", "", issn)
add_identifier("jstor", "[https://www.jstor.org/stable/", " →JSTOR]")
add_identifier("lccn", "", "", lccn)
add_identifier("oclc", "[https://search.worldcat.org/title/", " →OCLC]")
add_identifier("ol", "[https://openlibrary.org/works/OL", "/ →OL]")
add_identifier("pmid", "[https://www.ncbi.nlm.nih.gov/pubmed/", " →PMID]")
add_identifier("pmcid", "[https://www.ncbi.nlm.nih.gov/pmc/articles/", "/ →PMCID]")
add_identifier("ssrn", "[https://ssrn.com/abstract=", " →SSRN]")
-- add_identifier("urn", "", "", urn)
local id = a("id")
if id then
small(id)
end
archiveurl, archiveurl_fullname = aurl_with_name("archiveurl")
if archiveurl then
add(", archived from ")
local url, url_fullname = aurl_with_name("url")
if not url then
-- attempt to infer original URL from archive URL; this works at
-- least for Wayback Machine (web.archive.org) URL's
url = archiveurl:match("/(https?:.*)$")
if not url then
error(
("When |%s= is specified, |%s= must also be included"):format(archiveurl_fullname, url_fullname)
)
end
end
add("[" .. url .. " the original] on ")
local archivedate, archivedate_fullname = a_with_name("archivedate")
if archivedate then
add(format_date(archivedate))
elseif string.sub(archiveurl, 1, 28) == "https://web.archive.org/web/" then
-- If the archive is from the Wayback Machine, then it already contains the date
-- Get the date and format it
local wayback_date = string.sub(archiveurl, 29, 29 + 7)
wayback_date = string.sub(wayback_date, 1, 4)
.. "-"
.. string.sub(wayback_date, 5, 6)
.. "-"
.. string.sub(wayback_date, 7, 8)
add(format_date(wayback_date))
else
error(
("When |%s= is specified, |%s= must also be included"):format(
archiveurl_fullname,
archivedate_fullname
)
)
end
end
if a("accessdate") then
--Otherwise do not display here, as already used as a fallback for missing date= or year= earlier.
if (a("date") or a("nodate") or a("year")) and not a("archivedate") then
add(", retrieved " .. format_date(a("accessdate")))
end
end
local formatted_section = format_chapterlike("section", "section ")
if formatted_section then
add(", ")
add(formatted_section)
end
-- video game stuff
local system = parse_and_format_annotated_text("system")
if system then
add(", " .. system)
end
local scene = parse_and_format_annotated_text("scene")
if scene then
add(", scene: " .. scene)
end
local level = parse_and_format_annotated_text("level")
if level then
add(", level/area: " .. level)
end
local note = parse_and_format_annotated_text("note")
if note then
add(", " .. note)
end
local note_plain = parse_and_format_annotated_text("note_plain")
if note_plain then
add(" " .. note_plain)
end
-- Wrapper around format_numeric_param that inserts the formatted text with optional preceding text.
local function handle_numeric_param(paramname, singular_desc, pretext)
local numspec = format_numeric_param(paramname, singular_desc)
if numspec then
add((pretext or "") .. numspec)
end
end
handle_numeric_param("page", a("page_prefix") or "page", ", ")
handle_numeric_param("column", a("column_prefix") or "column", ", ")
handle_numeric_param("line", a("line_prefix") or "line", ", ")
-- FIXME: Does this make sense? What is other=?
local other = parse_and_format_annotated_text("other")
if other then
add(", " .. other)
end
end
local function add_authors(args, last_first)
-- Find maximum indexed author or last name.
local maxind = math.max(args.author.maxindex, args.last.maxindex)
-- Include max index of ancillary params so we get an error message about their use without the primary params.
local ancillary_params =
{"trans-author", "authorlink", "trans-authorlink", "first", "trans-first", "trans-last"}
for _, ancillary in ipairs(ancillary_params) do
maxind = math.max(maxind, args[ancillary].maxindex)
end
local num_authors = 0
for i = 1, maxind do
local ind = i == 1 and "" or i
local author, last = args.author[i], args.last[i]
if author or last then
local this_num_authors = add_author(
author,
"author" .. ind,
args["trans-author"][i],
"trans-author" .. ind,
args.authorlink[i],
"authorlink" .. ind,
args["trans-authorlink"][i],
"trans-authorlink" .. ind,
args.first[i],
"first" .. ind,
args["trans-first"][i],
"trans-first" .. ind,
last,
"last" .. ind,
args["trans-last"][i],
"trans-last" .. ind,
last_first
)
num_authors = num_authors + this_num_authors
sep = last_first and "; " or ", "
else
for _, cant_have in ipairs(ancillary_params) do
if args[cant_have][i] then
error(("Can't have |%s%s= without |author%s= or |last%s="):format(cant_have, ind, ind, ind))
end
end
end
end
return num_authors
end
local function add_newversion()
-- If there's a "newversion" section, add the new-version text.
if has_newversion() then
sep = nil
--Test for new version of work.
add(SEMICOLON_SPACE)
if args.newversion then -- newversion= is intended for English text, e.g. "quoted in" or "republished as".
add(args.newversion)
elseif not args.edition2 then
if has_new_title_or_author() then
add("republished as")
else
add("republished")
end
end
add(" ")
return ""
else
return ", "
end
end
------------------- Now we start outputting text ----------------------
local need_comma = false
-- Set this now so a() works just below.
get_full_paramname = make_get_full_paramname("")
if args.brackets then
add("[")
end
bold_year = not format_as_cite
year_last = format_as_cite
formatted_date, need_date = format_date_args(
a,
get_full_paramname,
alias_map,
nil,
nil,
bold_year,
"Can we [[:Category:Requests for date|date]] this quote?",
year_last
)
-- format_date_args bolds the year here (via bold_year = not format_as_cite), so this is the only date variable
-- we wrap; origdate=, date_published= and the newversion date are left unwrapped.
formatted_date = j_lazim(formatted_date)
-- Fetch origdate=/origyear=/origmonth= and format appropriately.
formatted_origdate = format_date_args(a, get_full_paramname, alias_map, "orig", nil, nil, nil, year_last)
local num_authors, need_date
if format_as_cite then
num_authors = add_authors(args, "last_first")
if author_outputted then
sep = " "
end
-- Display all the text that comes after the author, for the main portion.
postauthor("", num_authors, "format_as_cite")
author_outputted = false
sep = add_newversion()
-- Add the newversion author(s).
if args["2ndauthor"] or args["2ndlast"] then
num_authors = add_author(
args["2ndauthor"],
"2ndauthor",
nil,
nil,
args["2ndauthorlink"],
"2ndauthorlink",
nil,
nil,
args["2ndfirst"],
"2ndfirst",
nil,
nil,
args["2ndlast"],
"2ndlast",
nil,
nil,
"last_first"
)
sep = ", "
else
for _, cant_have in ipairs{"2ndauthorlink", "2ndfirst"} do
if args[cant_have] then
error(("Can't have |%s= without |2ndauthor= or |2ndlast="):format(cant_have))
end
end
end
-- Display all the text that comes after the author, for the "newversion" section.
postauthor(2, num_authors, "format_as_cite")
else
if formatted_date then
need_comma = true
add(formatted_date)
end
if formatted_origdate then
need_comma = true
add(SPACE_LBRAC .. formatted_origdate .. RBRAC)
end
if need_comma then
sep = ", "
end
date_outputted = true
num_authors = add_authors(args)
-- Display all the text that comes after the author, for the main portion.
postauthor("", num_authors)
author_outputted = false
sep = add_newversion()
-- Add the newversion author(s).
if args["2ndauthor"] or args["2ndlast"] then
num_authors = add_author(
args["2ndauthor"],
"2ndauthor",
nil,
nil,
args["2ndauthorlink"],
"2ndauthorlink",
nil,
nil,
args["2ndfirst"],
"2ndfirst",
nil,
nil,
args["2ndlast"],
"2ndlast",
nil,
nil
)
sep = ", "
else
for _, cant_have in ipairs{"2ndauthorlink", "2ndfirst"} do
if args[cant_have] then
error(("Can't have |%s= without |2ndauthor= or |2ndlast="):format(cant_have))
end
end
end
-- Display all the text that comes after the author, for the "newversion" section.
postauthor(2, num_authors)
end
if not args.nocolon then
sep = nil
add(":")
elseif args.usenodot and not args.nodot then
add(".")
end
-- Concatenate output portions to form output text.
local output_text = concat(output)
-- Remainder of code handles adding categories. We add one or more of the following categories:
--
-- 1. [[Category:LANG terms with quotations]], based on the first language code in termlang= or 1=. Added to
-- mainspace, Reconstruction: and Appendix: pages as well as Citations: pages if the corresponding mainspace
-- page exists. Not added if nocat= is given. Note that [[Module:usex]] adds the same category using the same
-- logic, but we do it here too because we may not have a quotation to format. (We add in those circumstances
-- because typically when there's no quotation to format, it's because it's formatted manually underneath the
-- citation, or using {{ja-x}}, {{th-x}} or similar.)
-- 2. [[Category:Requests for date in LANG entries]], based on the first language code in 1=. Added to mainspace,
-- Reconstruction:, Appendix: and Citations: pages unless nocat= is given.
-- 3. [[Category:Quotations using nocat parameter]], if nocat= is given. Added to mainspace, Reconstruction:,
-- Appendix: and Citations: pages.
local categories = {}
local termlang = get_first_lang(args.termlang or argslang)
if args.nocat then
if format_as_cite then
insert(tracking_categories, "Citations using nocat parameter")
else
insert(tracking_categories, "Quotations using nocat parameter")
end
else
-- don't create language tracking categories for citations
if not format_as_cite then
local title
if args.pagename then -- for testing, doc pages, etc.
title = mw.title.new(args.pagename)
if not title then
error(("Bad value for `args.pagename`: '%s'"):format(args.pagename))
end
else
title = mw.title.getCurrentTitle()
end
-- Only add [[Citations:foo]] to [[:Category:LANG terms with quotations]] if [[foo]] exists.
local ok_to_add_cat
if title.nsText ~= "Citations" then
ok_to_add_cat = true
else
local mainspace_title = mw.title.new(title.text)
if mainspace_title and mainspace_title.exists then
ok_to_add_cat = true
end
end
if ok_to_add_cat then
insert(categories, termlang:getFullName() .. " terms with quotations")
end
end
if need_date then
local argslangobj = get_first_lang(argslang)
insert(categories, "Requests for date in " .. argslangobj:getCanonicalName() .. " entries")
end
end
return output_text .. (
not lang and "" or
(#categories > 0 and format_categories(categories, lang, args.sort) or "") .. (
#tracking_categories > 0
and format_categories(
tracking_categories,
lang,
args.sort,
nil,
not page_should_be_ignored(mw.title.getCurrentTitle().fullText)
)
or ""
)
)
end
-- Alias specs for type= and type2=. Each spec is `{canon, aliases, with_newversion}` where `canon` is the canonical
-- parameter (with "2" added if type2= is being handled), `aliases` is a comma-separated string of aliases (with "2"
-- added if type2= is being handled, except for numeric params), and `with_newversion` indicates whether we should
-- process this spec if type2= is being handled.
local type_alias_specs = {
av = {
{"author", "writer,writers", true},
{"chapter", "episode", true},
{"chapterurl", "episodeurl", true},
{"trans-chapter", "trans-episode", true},
{"chapter_tlr", "episode_tlr,episode_tlrs,episode_translator,episode_translators", true},
{"chapter_series", "episode_series", true},
{"chapter_seriesvolume", "episode_seriesvolume", true},
{"chapter_number", "episode_number", true},
{"chapter_plain", "episode_plain", true},
{"volume", "season", true},
{"volumes", "seasons", true},
{"volume_plain", "season_plain", true},
{"volumeurl", "seasonurl", true},
{"platform", "network", true},
},
book = {
{"author", "3"},
{"editor", "amadekar"},
{"title", ",4"},
{"url", "5"},
{"year", "2"},
{"page", "6"},
{"text", "7"},
{"t", "8"},
},
journal = {
{"year", "2"},
{"author", "3"},
{"chapter", "title,sernav,article,meqale,4", true},
{"chapterurl", "titleurl,articleurl,urlya-sernav,urlya-meqaleyê", true},
{"trans-chapter", "trans-title,trans-article,tercimeya-sernav,tercimeya-meqaleyê", true},
{"chapter_tlr", "title_tlr,title_tlrs,title_translator,title_translators,article_tlr,article_tlrs,article_translator,article_translators,tercimanê-sernav,tercimanên-sernav,tercimanên-meqaleyê,tercimanê-meqaleyê", true},
{"chapter_series", "title_series,article_series,seriya-sernav,seriya-meqaleyê", true},
{"chapter_seriesvolume", "title_seriesvolume,article_seriesvolume,cildê-seriya-sernav,cildê-seriya-meqaleyê", true},
{"chapter_number", "title_number,article_number,hejmara-sernav,hejmara-meqaleyê", true},
{"chapter_plain", "title_plain,article_plain,sernav-sade,meqale-sade", true},
{"title", "journal,kovar,magazine,newspaper,work,xebat,xezete,5", true},
{"trans-title", "trans-journal,tercimeya-kovar,trans-magazine,trans-newspaper,tercimeya-xezete,trans-work,tercimeya-xebat", true},
{"tlr", "journal_tlr,tercimanê-kovarê,journal_tlrs,journal_translator,journal_translators,magazine_tlr,magazine_tlrs,magazine_translator,magazine_translators,newspaper_tlr,newspaper_tlrs,newspaper_translator,tercimanê-xezeteyê,newspaper_translators,work_tlr,tercimanê-xebatê,work_tlrs,work_translator,work_translators", true},
{"url", "6,journalurl,urlya-kovarê,magazineurl,newspaperurl,urlya-xezeteyê,workurl,urlya-xebatê", true},
{"page", "7"},
{"source", "newsagency,ajans,news_agency", true},
{"text", "8"},
{"t", "9"},
},
}
-- Process interally-handled aliases related to type= or type2=. `args` is a table of arguments; `typ` is the value of
-- type= or type2=; newversion=true if we're dealing with type2=; alias_map is used to keep track of alias mappings
-- seen.
local function process_type_aliases(args, typ, newversion, alias_map)
local ind = newversion and "2" or ""
local deprecated = ine(args.lang)
if not type_alias_specs[typ] then
local possible_values = {}
for possible, _ in pairs(type_alias_specs) do
insert(possible_values, possible)
end
sort(possible_values)
error(
("Unrecognized value '%s' for type%s=; possible values are %s"):format(
typ,
ind,
concat(possible_values, ",")
)
)
end
for _, alias_spec in ipairs(type_alias_specs[typ]) do
local canon, aliases, with_newversion = unpack(alias_spec)
if with_newversion or not newversion then
canon = canon .. ind
aliases = split(aliases, ",", true)
local saw_alias = nil
for _, alias in ipairs(aliases) do
if alias:match("^%d+$") then
alias = tonumber(alias)
if deprecated then
alias = alias - 1
end
else
alias = alias .. ind
end
if args[alias] then
if saw_alias == nil then
saw_alias = alias
else
error(("|%s= and |%s= are aliases; cannot specify a value for both"):format(saw_alias, alias))
end
end
end
if saw_alias and (not newversion or type(saw_alias) == "string") then
if args[canon] then
error(("|%s= is an alias of |%s=; cannot specify a value for both"):format(saw_alias, canon))
end
args[canon] = args[saw_alias]
-- Wipe out the original after copying. This is important in case of a param that has general significance
-- but has been redefined (e.g. {{quote-av}} redefines number= for the episode number, and
-- {{quote-journal}} redefines title= for the chapter= (article). It's also important due to unhandled
-- parameter checking.
args[saw_alias] = nil
alias_map[canon] = saw_alias
end
end
end
end
-- Clone and combine frame's and parent's args while also assigning nil to empty strings. Handle aliases and ignores.
local function clone_args(direct_args, parent_args)
local args = {}
-- Processing parent args must come first so that direct args override parent args. Note that if a direct arg is
-- specified but is blank, it will still override the parent arg (with nil).
for pname, param in pairs(parent_args) do
-- [[Special:WhatLinksHere/Wiktionary:Tracking/quote/param/PARAM]]
track("param/" .. pname)
args[pname] = ine(param)
end
-- Process ignores. The value of `ignore` is a comma-separated list of parameter names to ignore (erase). We need to
-- do this before aliases due to {{quote-song}}, which sets chapter= to the value of title= in the direct params and
-- sets title= to the value of album= using an alias. If we do the ignores after aliases, we get an error during alias
-- processing, saying that title= and its alias album= are both present.
local ignores = ine(direct_args.ignore)
if ignores then
for ignore in gsplit(ignores, "%s*,%s*") do
args[ignore] = nil
end
end
local alias_map = {}
local other_controls = {}
-- noroman
local noroman = ine(direct_args.noroman)
if noroman then
other_controls.overrides = other_controls.overrides or {}
for param in gsplit(noroman, "%s*,%s*") do
other_controls.overrides[param] = (other_controls.overrides[param] or {})
other_controls.overrides[param].noroman = true
end
end
-- Process internally-specified aliases using type= or type2=.
local typ = args.type or direct_args.type or args.cure or direct_args.cure
if typ then
process_type_aliases(args, typ, false, alias_map)
end
local typ2 = args.type2 or direct_args.type2 or args.cure2 or direct_args.cure2
if typ2 then
process_type_aliases(args, typ2, true, alias_map)
end
-- Process externally-specified aliases. The value of `alias` is a list of semicolon-separated specs, each of which
-- is of the form DEST:SOURCE,SOURCE,... where DEST is the canonical name of a parameter and SOURCE refers to an
-- alias. Whitespace is allowed between all delimiters. The order of aliases may be important. For example, for
-- {{quote-journal}}, title= contains the article name and is an alias of underlying chapter=, while journal= or
-- work= contains the journal name and is an alias of underlying title=. As a result, the title -> chapter alias
-- must be specified before the journal/work -> title alias.
--
-- Whenever we copy a value from argument SOURCE to argument DEST, we record an entry for the pair in alias_map, so
-- that when we would display an error message about DEST, we display SOURCE instead.
--
-- Do alias processing (and ignore and error_if processing) before processing direct_args so that e.g. we can set up
-- an alias of title -> chapter and then set title= to something else in the direct args ({{quote-hansard}} does
-- this).
--
-- FIXME: Delete this once we've converted all alias processing to internal.
local aliases = ine(direct_args.alias)
if aliases then
-- Allow and discard a trailing semicolon, to make managing multiple aliases easier.
aliases = ugsub(aliases, "%s*;$", "")
for alias_spec in gsplit(aliases, "%s*;%s*") do
local alias_spec_parts = split(alias_spec, "%s*:%s*")
if #alias_spec_parts ~= 2 then
error(("Alias spec '%s' should have one colon in it"):format(alias_spec))
end
local dest, sources = unpack(alias_spec_parts)
sources = split(sources, "%s*,%s*")
local saw_source = nil
for _, source in ipairs(sources) do
if source:match("^%d+$") then
source = tonumber(source)
end
if args[source] then
if saw_source == nil then
saw_source = source
else
error(("|%s= and |%s= are aliases; cannot specify a value for both"):format(saw_source, source))
end
end
end
if saw_source then
if args[dest] then
error(("|%s= is an alias of |%s=; cannot specify a value for both"):format(saw_source, dest))
end
args[dest] = args[saw_source]
-- Wipe out the original after copying. This important in case of a param that has general significance
-- but has been redefined (e.g. {{quote-av}} redefines number= for the episode number, and
-- {{quote-journal}} redefines title= for the chapter= (article). It's also important due to unhandled
-- parameter checking.
args[saw_source] = nil
alias_map[dest] = saw_source
end
end
end
-- Process error_if. The value of `error_if` is a comma-separated list of parameter names to throw an error if seen
-- in parent_args (they are params we overwrite in the direct args).
local error_ifs = ine(direct_args.error_if)
if error_ifs then
for error_if in gsplit(error_ifs, "%s*,%s*") do
if ine(parent_args[error_if]) then
error(
("Cannot specify a value |%s=%s as it would be overwritten or ignored"):format(
error_if,
ine(parent_args[error_if])
)
)
end
end
end
for pname, param in pairs(direct_args) do
-- ignore control params
if pname ~= "ignore" and pname ~= "alias" and pname ~= "error_if" and pname ~= "noroman" then
args[pname] = ine(param)
end
end
return args, alias_map, other_controls
end
local function get_args(frame_args, parent_args, require_lang)
-- FIXME: We are processing arguments twice, once in clone_args() and then again in [[Module:parameters]]. This is
-- wasteful of memory.
local cloned_args, alias_map, other_controls = clone_args(frame_args, parent_args)
local deprecated = ine(parent_args.lang)
local alias_of_t = {alias_of = "t"}
local boolean = {type = "boolean"}
local language_sublist = {type = "language", sublist = true}
local list_allow_holes = {list = true, allow_holes = true}
local script = {type = "script"}
local function alias_list_allow_holes(canon)
return {alias_of = canon, list = true, allow_holes = true}
end
-- First, the "single" params that don't have FOO2 or FOOn versions.
local params = {
[deprecated and "lang" or 1] = {required = require_lang, type = "language", sublist = true, default = "und"},
["lang2"] = language_sublist,
["ziman2"] = {alias_of = "lang2", type = "language", sublist = true},
["newversion"] = true,
["versiyona-nû"] = {alias_of = "newversion"},
["author"] = list_allow_holes,
["nivîskar"] = alias_list_allow_holes("author"),
["2ndauthor"] = true,
["2nivîskar"] = {alias_of = "2ndauthor"},
["trans-author"] = list_allow_holes,
["tercimeya-nivîskar"] = alias_list_allow_holes("trans-author"),
["authorlink"] = list_allow_holes,
["lînka-nivîskar"] = alias_list_allow_holes("authorlink"),
["2ndauthorlink"] = true,
["2lînka-nivîskar"] = {alias_of = "2ndauthorlink"},
["trans-authorlink"] = list_allow_holes,
["lînka-tercimeya-nivîskar"] = alias_list_allow_holes("trans-authorlink"),
["first"] = list_allow_holes,
["pêşnav"] = alias_list_allow_holes("first"),
["2ndfirst"] = true,
["2pêşnav"] = {alias_of = "2ndfirst"},
["trans-first"] = list_allow_holes,
["tercimeya-pêşnav"] = alias_list_allow_holes("trans-first"),
["last"] = list_allow_holes,
["paşnav"] = alias_list_allow_holes("last"),
["2ndlast"] = true,
["2paşnav"] = {alias_of = "2ndlast"},
["trans-last"] = list_allow_holes,
["tercimeya-paşnav"] = alias_list_allow_holes("trans-last"),
["nocat"] = boolean,
["nekat"] = {alias_of = "nocat"},
["nocolon"] = boolean,
-- quote params
text = true,
passage = {alias_of = "text"},
["jêgirtin"] = {alias_of = "text"},
["jg"] = {alias_of = "text"},
tr = true,
transliteration = {alias_of = "tr"},
ts = true,
transcription = {alias_of = "ts"},
norm = true,
normalization = {alias_of = "norm"},
sc = script,
normsc = script,
sort = true,
subst = true,
footer = true,
lit = true,
["hrf"] = {alias_of = "lit"},
t = true,
translation = alias_of_t,
["tercime"] = alias_of_t,
["werger"] = alias_of_t,
["w"] = alias_of_t,
gloss = alias_of_t,
lb = true,
["e"] = {alias_of = "lb"},
brackets = boolean,
-- original quote params
origtext = true,
["metna-orîjînal"] = {alias_of = "origtext"},
origtr = true,
["tr-orîjînal"] = {alias_of = "origtr"},
origts = true,
["ts-orîjînal"] = {alias_of = "origts"},
orignorm = true,
["norm-orîjînal"] = {alias_of = "orignorm"},
origsc = script,
["sc-orîjînal"] = {alias_of = "origsc"},
orignormsc = script,
["normsc-orîjînal"] = {alias_of = "orignormsc"},
origsubst = true,
["subst-orîjînal"] = {alias_of = "origsubst"},
origlb = true,
["e-orîjînal"] = {alias_of = "origlb"},
usenodot = boolean,
nodot = boolean,
inline = boolean,
asquote = boolean,
ascite = boolean,
}
-- Most params have PARAM2 variants for use with `newversion`. FIXME: We should generalize this to
-- allow PARAMN variants for any N.
local function add_with_2(param, value)
params[param] = value
params[param .. "2"] = value
end
local function alias_with_2(alias, canon)
params[alias] = {alias_of = canon}
params[alias .. "2"] = {alias_of = canon .. "2"}
end
for _, param12 in ipairs{
"worklang",
"termlang",
"origlang",
"origworklang"
} do
add_with_2(param12, language_sublist)
end
-- Then the newversion params (which have FOO2 versions).
for _, param12 in ipairs{
-- author-like params; author params themselves are either list params (author=, last=, etc.) or single params
-- (2ndauthor=, 2ndlast=, etc.)
"coauthors",
"quotee",
"tlr",
"editor",
"editors",
"mainauthor",
"compiler",
"compilers",
"director",
"directors",
"lyricist",
"lyrics-translator",
"composer",
"role",
"actor",
"artist",
"feat",
-- author control params
"default-authorlabel",
"authorlabel",
-- title
"title",
"trans-title",
"series",
"seriesvolume",
"notitle",
-- entry
"entry",
"entryurl",
"trans-entry",
-- chapter
"chapter",
"chapterurl",
"chapter_number",
"chapter_plain",
"chapter_series",
"chapter_seriesvolume",
"trans-chapter",
"chapter_tlr",
-- section
"section",
"sectionurl",
"section_number",
"section_plain",
"section_series",
"section_seriesvolume",
"trans-section",
-- other video-game params
"system",
"scene",
"level",
-- URL
"url",
"urls",
"archiveurl",
-- edition
"edition",
"edition_plain",
-- ID params
"bibcode",
"doi",
"isbn",
"issn",
"jstor",
"lccn",
"oclc",
"ol",
"pmid",
"pmcid",
"ssrn",
"urn",
"id",
-- misc date params; most date params handled below
"archivedate",
"accessdate",
"nodate",
-- numeric params handled below
-- other params
"type",
"genre",
"format",
"medium",
"others",
"quoted_in",
"location",
"publisher",
"original",
"by",
"deriv",
"note",
"note_plain",
"other",
"source",
"platform",
} do
add_with_2(param12, true)
end
-- Then the aliases of newversion params (which have FOO2 versions).
for _, param12_aliased in ipairs{
{"role", "roles"},
{"role", "speaker"},
{"tlr", "tlrs"},
{"tlr", "translator"},
{"tlr", "translators"},
{"chapter_tlr", "chapter_tlrs"},
{"chapter_tlr", "chapter_translator"},
{"chapter_tlr", "chapter_translators"},
{"doi", "DOI"},
{"isbn", "ISBN"},
{"issn", "ISSN"},
{"jstor", "JSTOR"},
{"lccn", "LCCN"},
{"oclc", "OCLC"},
{"ol", "OL"},
{"pmid", "PMID"},
{"pmcid", "PMCID"},
{"ssrn", "SSRN"},
{"urn", "URN"},
} do
local canon, alias = unpack(param12_aliased)
alias_with_2(alias, canon)
end
-- tercimeyên Then the aliases of newversion params (which have FOO2 versions).
for _, param12_aliased in ipairs{
{"role", "rol"},
{"role", "axivêr"},
{"tlr", "terciman"},
{"tlr", "wergêr"},
{"chapter_tlr", "tercimanên-beşê"},
{"chapter_tlr", "tercimanê-beşê"},
{"chapter_tlr", "tercimana-beşê"},
{"chapter_tlr", "wergêrên-beşê"},
{"chapter_tlr", "wergêrê-beşê"},
{"chapter_tlr", "wergêra-beşê"},
-- tercimeyên Then the newversion params (which have FOO2 versions).
-- author-like params; author params themselves are either list params (author=, last=, etc.) or single params
-- (2ndauthor=, 2ndlast=, etc.)
{"coauthors", "hevnivîskar"},
{"quotee", "jê-tê-girtin"},
{"editor", "edîtor"},
{"editors", "edîtor"},
{"editor", "amadekar"},
{"mainauthor", "nivîskarê-sereke"},
{"compiler", "berhevkar"},
{"compilers", "berhevkar"},
{"director", "derhêner"},
{"directors", "derhêner"},
{"lyricist", "gotinnivîs"},
{"lyrics-translator", "tercimanê-gotinan"},
{"lyrics-translator", "tercimana-gotinan"},
{"lyrics-translator", "wergêrê-gotinan"},
{"lyrics-translator", "wergêra-gotinan"},
{"composer", "bestekar"},
{"actor", "aktor"},
{"actor", "lîstikvan"},
{"artist", "hunermend"},
{"artist", "artîst"},
{"feat", "mêvan"},
--tercimeyên author control params
{"default-authorlabel", "standard-etîketa-nivîskar"},
{"authorlabel", "etîketa-nivîskar"},
--tercimeyên title
{"title", "sernav"},
{"trans-title", "tercimeya-sernav"},
{"trans-title", "wergera-sernav"},
{"series", "serî"},
{"series", "rêze"},
{"seriesvolume", "cildê-seriyê"},
{"seriesvolume", "cildê-rêzeyê"},
{"notitle", "bêsernav"},
--tercimeyên entry
{"entry", "made"},
{"entryurl", "urlya-madeyê"},
{"trans-entry", "tercimeya-madeyê"},
{"trans-entry", "wergera-madeyê"},
--tercimeyên chapter
{"chapter", "beş"},
{"chapterurl", "urlya-beşê"},
{"chapter_number", "hejmara-beşê"},
{"chapter_plain", "beş-sade"},
{"chapter_series", "seriya-beşê"},
{"chapter_series", "rêzeya-beşê"},
{"chapter_seriesvolume", "cildê-seriya-beşê"},
{"chapter_seriesvolume", "cildê-rêzeya-beşê"},
{"trans-chapter", "tercimeya-beşê"},
{"trans-chapter", "wergera-beşê"},
--tercimeyên section
{"section", "qisim"},
{"section", "qism"},
{"sectionurl", "urlya-qismê"},
{"section_number", "hejmara-qismê"},
{"section_plain", "qisim-sade"},
{"section_plain", "qism-sade"},
{"section_series", "rêzeya-qismê"},
{"section_seriesvolume", "cildê-seriya-qismê"},
{"section_seriesvolume", "cildê-rêzeya-qismê"},
{"trans-section", "tercimeya-qismê"},
--tercimeyên other video-game params
{"system", "sîstem"},
{"system", "pergal"},
{"scene", "sehne"},
--{"level", "level"},
--tercimeyên URL
{"archiveurl", "urlya-arşîvê"},
-- tercimeyên edition
{"edition", "çap"},
{"edition_plain", "çap-sade"},
--tercimeyên misc date params; most date params handled below
{"archivedate", "roja-arşîvê"},
{"archivedate", "tarîxa-arşîvê"},
{"accessdate", "tarîxa-gihiştinê"},
{"accessdate", "roja-gihiştinê"},
{"nodate", "bêtarîx"},
--tercimeyên other params
{"type", "cure"},
{"genre", "janr"},
{"medium", "medya"},
{"others", "kesên-din"},
{"others", "yên-din"},
{"quoted_in", "cihê-jêgirtinê"},
{"location", "cih"},
{"location", "cîh"},
{"publisher", "weşanxane"},
{"publisher", "weşanger"},
{"original", "orîjînal"},
{"original", "orjînal"},
{"by", "ji-aliyê"},
{"by", "ji-hêla"},
--{"deriv", ""},
{"note", "not"},
{"note_plain", "not-sade"},
{"other", "yê-din"},
{"source", "çavkanî"},
--tercimeyên
{"worklang", "zimanê-eserê"},
{"termlang", "zimanê-termê"},
{"origlang", "zimanê-orîjînal"},
{"origworklang", "zimanê-esera-orîjînal"},
} do
local canon, alias = unpack(param12_aliased)
alias_with_2(alias, canon)
end
-- Then the date params.
for _, datelike in ipairs{{"", ""}, {"orig", ""}, {"", "_published"}} do
local pref, suf = unpack(datelike)
for _, arg in ipairs{"date", "year", "month", "start_date", "start_year", "start_month"} do
add_with_2(pref .. arg .. suf, true)
end
end
-- Kurmancî aliases for the date params
for _, date_aliased in ipairs{
{"date", "tarîx"},
{"date", "dîrok"},
{"year", "sal"},
{"month", "meh"},
{"start_date", "tarîxa-destpêkê"},
{"start_year", "sala-destpêkê"},
{"start_month", "meha-destpêkê"},
{"origdate", "tarîxa-orîjînal"},
{"origyear", "sala-orîjînal"},
{"origyear", "sala orîjînal"},
{"origmonth", "meha-orîjînal"},
{"origstart_date", "tarîxa-destpêkê-orîjînal"},
{"origstart_year", "sala-destpêkê-orîjînal"},
{"origstart_month", "meha-destpêkê-orîjînal"},
{"date_published", "tarîxa-weşandinê"},
{"year_published", "sala-weşandinê"},
{"year_published", "sala weşandinê"},
{"month_published", "meha-weşandinê"},
{"start_date_published", "tarîxa-destpêkê-weşandin"},
{"start_year_published", "sala_destpêkê-weşandin"},
{"start_month_published", "meha-destpêkê-weşandin"}
} do
local canon, alias = unpack(date_aliased)
alias_with_2(alias, canon)
end
local numeric_param_suffixes = {"", "s", "_plain", "url", "_prefix"}
-- Then the numeric params.
for _, numeric in ipairs{"volume", "issue", "number", "line", "page", "column"} do
for _, suf in ipairs(numeric_param_suffixes) do
add_with_2(numeric .. suf, true)
end
end
-- canon word -> Kurdish forms
-- aliases : plain synonyms (maps straight to canon, e.g. "volume")
-- sade : ezafe form used before "-sade" (canon .. "_plain")
-- urlya : ezafe form used after "urlya-" (canon .. "url")
local ku_numeric = {
volume = {aliases = {"cild", "cîld"}, sade = "cild", urlya = "cild"},
issue = {aliases = {"hj", "hejmar"}, sade = "hejmar", urlya = "hejmarê"},
number = {aliases = {"hj", "hejmar"}, sade = "hejmar", urlya = "hejmarê"},
line = {aliases = {"rêz"}, sade = "rêz", urlya = "rêzê"},
page = {aliases = {"rûpel", "r"}, sade = "rûpel", urlya = "rûpelê"},
column = {aliases = {"stûn"}, sade = "stûn", urlya = "stûnê"},
}
for canon, t in pairs(ku_numeric) do
for _, alias in ipairs(t.aliases) do
alias_with_2(alias, canon)
end
alias_with_2(t.sade .. "-sade", canon .. "_plain")
alias_with_2("urlya-" .. t.urlya, canon .. "url")
alias_with_2("pêşgira-" .. t.urlya, canon .. "_prefix")
end
-- And the aliases of numeric params.
for _, numeric_aliased in ipairs{{"volume", "vol"}} do
local canon, alias = unpack(numeric_aliased)
for _, suf in ipairs(numeric_param_suffixes) do
alias_with_2(alias .. suf, canon .. suf)
end
end
return process_params(cloned_args, params), alias_map, other_controls
end
local function get_origtext_params(args)
local origtext, origtextlang, origsc, orignormsc
if args.origtext then
-- Wiktionary language codes have at least two lowercase letters followed possibly by lowercase letters and/or
-- hyphens (there are more restrictions but this is close enough). Also check for nonstandard Latin etymology
-- language codes (e.g. VL. or LL.). (There used to be more nonstandard codes but they have all been
-- eliminated.)
origtextlang, origtext = args.origtext:match("^(%l%l[%l-]*):([^ ].*)$")
if not origtextlang then
-- Special hack for Latin variants, which can have nonstandard etym codes, e.g. VL., LL.
origtextlang, origtext = args.origtext:match("^(%uL%.):([^ ].*)$")
end
if not origtextlang then
error("origtext= should begin with a language code prefix")
end
origtextlang = get_lang(origtextlang, nil, "allow etym") or
error("origtext= should begin with a language code prefix")
origsc = args.origsc
orignormsc = args.orignormsc
else
for _, noparam in ipairs{"origtr", "origts", "origsc", "orignorm", "orignormsc", "origsubst", "origlb"} do
if args[noparam] then
error(("Cannot specify %s= without origtext="):format(noparam))
end
end
end
return origtext, origtextlang, origsc, orignormsc
end
local function get_quote(args, is_cite, source)
local text = args.text
local gloss = args.t
local tr = args.tr
local ts = args.ts
local norm = args.norm
local sc = args.sc
local normsc = args.normsc
-- Fetch original-text parameters.
local origtext, origtextlang, origsc, orignormsc = get_origtext_params(args)
-- If any quote-related args are present, display the actual quote; otherwise, display nothing.
if text or gloss or tr or ts or norm or args.origtext then
-- Pass "und" here rather than cause an error; there will be an error on mainspace, Citations, etc. pages
-- in any case in source() if the language is omitted.
local lang = get_first_lang(args[1] or args.lang)
local termlang = args.termlang and get_first_lang(args.termlang) or lang
local usex_data = {
lang = lang,
termlang = termlang,
usex = text,
sc = sc,
translation = gloss,
normalization = norm,
normsc = normsc,
transliteration = tr,
transcription = ts,
brackets = args.brackets,
subst = args.subst,
lit = args.lit,
footer = args.footer,
qq = parse_and_format_labels(args.lb, lang),
quote = "quote-meta",
orig = origtext,
origlang = origtextlang,
origsc = origsc,
orignorm = args.orignorm,
orignormsc = orignormsc,
origtr = args.origtr,
origts = args.origts,
origsubst = args.origsubst,
origqq = parse_and_format_labels(args.origlb, lang),
noreq = args.noreq,
nocat = is_cite or args.nocat,
source = source
}
if args.inline then
-- don't let usex format the footer, otherwise it gets inlined with the rest of the quoted text
usex_data.footer = nil
usex_data.inline = 1
text = format_usex(usex_data)
if text then
text = " “" .. text .. "”"
else
text = ""
end
if args.footer then
text = text .. "<dl><dd>" .. args.footer .. "</dd></dl>"
end
else
text = format_usex(usex_data)
end
elseif args.footer then
text = "<dl><dd>" .. args.footer .. "</dd></dl>"
end
return text
end
-- External interface, meant to be called from a template. Replaces {{quote-meta}} and meant to be the primary
-- interface for {{quote-*}} templates.
function export.quote_t(frame)
local args, alias_map, other_controls = get_args(frame.args, frame:getParent().args, "require_lang")
if args.ascite then
return export.cite_t(frame)
end
return show_quote(args, alias_map, other_controls)
end
function show_quote(args, alias_map, other_controls)
local parts = {}
insert(parts, get_quote(args, false, export.source(args, alias_map, nil, other_controls)))
local retval = concat(parts)
return deprecated and frame:expandTemplate({
title = "check deprecated lang param usage",
args = {retval, lang = args.lang},
}) or retval
end
-- External interface, meant to be called from a template. Replaces {{cite-meta}} and meant to be the primary
-- interface for {{cite-*}} templates.
function export.cite_t(frame)
local parent_args = {}
for k, v in pairs(frame:getParent().args) do
parent_args[k] = v
end
-- use "und" as lang if none provided
if parent_args[1] == nil then
parent_args[1] = "und"
end
local args, alias_map, other_controls = get_args(frame.args, parent_args)
if args.asquote then
return show_quote(args, alias_map, other_controls)
end
return show_cite(args, alias_map, other_controls)
end
function show_cite(args, alias_map, other_controls)
local parts = {}
-- don't nag for translations
if args.text and not args.t then
args.noreq = 1
end
local len_visible = args.text and ulen((args.text:gsub("<[^<>]+>", ""))) or 0
if len_visible == 0 then
if not args.t or args.t == "-" then
args.nocolon = true
end
elseif args.inline == nil then
args.inline = not (
args.block_text
or len_visible > 300
or (args.t and not args.t == "-" and len_visible < 80)
or string.match(args.text, "<br>")
)
end
insert(parts, '<span class="citation-whole"><span class="cited-source">')
insert(parts, export.source(args, alias_map, "format_as_cite", other_controls))
insert(parts, "</span>")
insert(parts, get_quote(args, "is_cite"))
insert(parts, "</span>")
local retval = concat(parts)
return deprecated
and frame:expandTemplate({
title = "check deprecated lang param usage",
args = {retval, lang = args.lang},
})
or retval
end
-- External interface, meant to be called from a template.
function export.call_quote_template(frame)
return export.call_template(frame)
end
-- External interface, for calling a template with overloaded parameters.
function export.overload(frame)
local iargs, overloaded_args = process_params(frame.args, {
[1] = {required = true}
}, true)
local parent_args = frame:getParent().args
for k, v in pairs(parent_args) do
overloaded_args[k] = v
end
return frame:expandTemplate{title = iargs[1], args = overloaded_args}
end
-- External interface, meant to be called from a template.
function export.call_template(frame)
local parameter_sublist = {type = "parameter", sublist = true}
local iargs, other_direct_args = process_params(frame.args, {
["template"] = true,
["textparam"] = parameter_sublist,
["pageparam"] = parameter_sublist,
["propagateparams"] = parameter_sublist,
["allowparams"] = {sublist = true}, -- Doesn't use type = "parameter", because any that end in :list get processed differently.
}, true)
local function fetch_param(source, params)
for _, param in ipairs(params) do
if source[param] then
return source[param]
end
end
return nil
end
local params = {
["text"] = true,
["passage"] = true,
["footer"] = true, -- always propagates, but gets special formatting
}
-- paramaters that can be used during template declaration or included in template calls
-- if the template is called with param=- it will set any declared value to nil
local always_propagate = {
["brackets"] = true,
["usenodot"] = true,
["nodot"] = true,
["ascite"] = true,
["asquote"] = true,
}
for k,v in pairs(always_propagate) do
params[k] = v
end
local textparam = iargs.textparam or {}
for _, param in ipairs(textparam) do
params[param] = true
end
local pageparam = iargs.pageparam or {}
local pageparam1 = pageparam[1]
if pageparam1 ~= nil then
params["page"], params["pages"] = true, true
for _, param in ipairs(pageparam) do
params[param] = true
end
end
local allowparams, allow_all, list = iargs.allowparams, false
if allowparams ~= nil then
for _, allow in ipairs(allowparams) do
local param = allow:match("^(.*):list$")
if param then
if list == nil then
list = {list = true}
end
params[scribunto_parameter_key(param)] = list
elseif allow == "*" then
track("no parameter checking")
allow_all = true
else
params[scribunto_parameter_key(allow)] = true
end
end
else
local parent = frame:getParent()
local template_name = parent:getTitle()
local template_title = new_title(template_name)
local content = template_title:getContent()
local allowed_params, seen = {}, {}
-- Detect all params used by the parent template. param:get_name() takes the
-- parent frame arg table as an argument so that preprocessing will take
-- them into account, since it will matter if the name contains another
-- parameter (e.g. the outer param in "{{{foo{{{bar}}}baz}}}" will change
-- depending on the value for bar=). `seen` memoizes results based on the
-- raw parameter text (which is stored as a string in the parameter object),
-- which avoids unnecessary param:get_name() calls, which are non-trivial.
for param in find_parameters(content) do
local raw = param.raw
if not seen[raw] then
allow = param:get_name(template_args)
params[scribunto_parameter_key(allow)] = true
seen[raw] = true
end
end
end
local propagateparams = iargs.propagateparams or {}
for _, param in ipairs(propagateparams) do
params[param] = true
end
local parent_args = frame:getParent().args
local args = process_params(parent_args, params, allow_all)
parent_args = shallow_copy(parent_args)
if textparam[1] ~= "-" then
other_direct_args.passage = args.text or args.passage or fetch_param(args, textparam)
end
if not (pageparam1 == nil or pageparam1 == "-") then
other_direct_args.page = fetch_param(args, pageparam) or args.page or other_direct_args.page or nil
other_direct_args.pages = args.pages or other_direct_args.pages or nil
if other_direct_args.page == "-" then
other_direct_args.page = nil
end
if other_direct_args.pages == "-" then
other_direct_args.pages = nil
end
end
if args.footer then
other_direct_args.footer = frame:expandTemplate{title = "small", args = {args.footer}}
end
for param, _ in pairs(always_propagate) do
if args[param] == "-" then
other_direct_args[param] = nil
else
other_direct_args[param] = args[param] or other_direct_args[param] or nil
end
end
-- authorlink=- can be used to prevent copying of author= to authorlink= but we don't want to propagate this to
-- the actual {{quote-*}} code.
if other_direct_args.authorlink == "-" then
other_direct_args.authorlink = nil
end
for _, param in ipairs(propagateparams) do
if args[param] then
other_direct_args[param] = args[param]
end
end
return frame:expandTemplate{title = iargs.template or "quote-book", args = other_direct_args}
end
local paramdoc_param_replacements = {
passage = {
param_with_synonym = "<<synonym>>, {{para|text}}, or {{para|passage}}",
param_no_synonym = "{{para|text}} or {{para|passage}}",
text = [=[
* <<params>> – the passage to be quoted.]=],
},
page = {
param_with_synonym = "<<synonym>> or {{para|page}}, or {{para|pages}}",
param_no_synonym = "{{para|page}} or {{para|pages}}",
text = [=[
* <<params>> – '''mandatory in some cases''': the page number(s) quoted from. When quoting a range of pages, note the following:
** Separate the first and last pages of the range with an [[en dash]], like this: {{para|pages|10–11}}.
** You must also use {{para|pageref}} to indicate the page to be linked to (usually the page on which the Wiktionary entry appears).
: This parameter must be specified to have the template link to the online version of the work.]=],
},
page_with_roman_preface = {
param_with_synonym = {"inherit", "page"},
param_no_synonym = {"inherit", "page"},
text = [=[
* <<params>> – '''mandatory in some cases''': the page number(s) quoted from. If quoting from the preface, specify the page number(s) in lowercase Roman numerals. When quoting a range of pages, note the following:
** Separate the first and last page number of the range with an [[en dash]], like this: {{para|pages|10–11}} or {{para|pages|iii–iv}}.
** You must also use {{para|pageref}} to indicate the page to be linked to (usually the page on which the Wiktionary entry appears).
: This parameter must be specified to have the template link to the online version of the work.]=],
},
chapter = {
param_with_synonym = "<<synonym>> or {{para|chapter}}",
param_no_synonym = "{{para|chapter}}",
text = [=[
* <<params>> – the name of the chapter quoted from.]=],
},
roman_chapter = {
param_with_synonym = {"inherit", "chapter"},
param_no_synonym = {"inherit", "chapter"},
text = [=[
* <<params>> – the chapter number quoted from in uppercase Roman numerals.]=],
},
arabic_chapter = {
param_with_synonym = {"inherit", "chapter"},
param_no_synonym = {"inherit", "chapter"},
text = [=[
* <<params>> – the chapter number quoted from in Arabic numerals.]=],
},
trailing_params = {
text = [=[
* {{para|footer}} – a comment on the passage quoted.
* {{para|brackets}} – use {{para|brackets|on}} to surround a quotation with [[bracket#Noun|brackets]]. This indicates that the quotation either contains a mere mention of a term (for example, “some people find the word '''''manoeuvre''''' hard to spell”) rather than an actual use of it (for example, “we need to '''manoeuvre''' carefully to avoid causing upset”), or does not provide an actual instance of a term but provides information about related terms.]=],
},
}
function export.paramdoc(frame)
local parargs = frame:getParent().args
local args = process_params(parargs, {
[1] = true,
})
local text = args[1]
local function do_param_with_optional_synonym(param, text_to_sub, paramtext_synonym, paramtext_no_synonym)
local function sub_param(synonym)
local subbed_paramtext
if synonym then
subbed_paramtext = paramtext_synonym:gsub("<<synonym>>", "{{para|" .. replacement_escape(synonym) .. "}}")
else
subbed_paramtext = paramtext_no_synonym
end
return frame:preprocess((text_to_sub:gsub("<<params>>", replacement_escape(subbed_paramtext))))
end
text = text:gsub("<<" .. pattern_escape(param) .. ">>", function()
return sub_param()
end)
text = text:gsub("<<" .. pattern_escape(param) .. ":(.-)>>", sub_param)
end
local function fetch_text(param_to_replace, key)
local spec = paramdoc_param_replacements[param_to_replace]
local val = spec[key]
if type(val) == "string" then
return val
end
if type(val) == "table" and val[1] == "inherit" then
return fetch_text(val[2], key)
end
error(
"Internal error: Unrecognized value for param '"
.. param_to_replace
.. "', key '"
.. key
.. "': "
.. mw.dumpObject(val)
)
end
for param_to_replace, spec in pairs(paramdoc_param_replacements) do
if not spec.param_no_synonym then
-- Text to substitute directly.
text = text:gsub("<<" .. pattern_escape(param_to_replace) .. ">>", function()
return frame:preprocess(fetch_text(param_to_replace, "text"))
end)
else
do_param_with_optional_synonym(
param_to_replace,
fetch_text(param_to_replace, "text"),
fetch_text(param_to_replace, "param_with_synonym"),
fetch_text(param_to_replace, "param_no_synonym")
)
end
end
-- Remove final newline so template code can add a newline after invocation
text = text:gsub("\n$", "")
return text
end
return export
72arc6j2gne95e89f35bo3s7eswd1vq
MediaWiki:Gadget-start.js/data.json
8
1357737
6291579
6276921
2026-07-26T17:21:16Z
Wikihez
7344
6291579
json
application/json
{
"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ê û nêr",
"mnmane": "mê yan nêr li gorî maneyê",
"g": "giştî",
"y": "yekjimar",
"p": "pirjimar"
},
"tplDefs": [
{
"name": "awayê Jacobsonê",
"langs": [
"zza"
]
},
{
"name": "rastnivîs",
"langs": [
"ku"
],
"noLang": true
},
{
"name": "ku-Latn",
"langs": [
"ku"
],
"noLang": true
},
{
"name": "sdh-Arab",
"langs": [
"sdh"
],
"noLang": true
},
{
"name": "hac-Arab",
"langs": [
"hac"
],
"noLang": true
},
{
"name": "ja-romanîzekirin",
"langs": [
"ja"
],
"noLang": true
},
{
"name": "ckb-Arab",
"langs": [
"ckb"
],
"noLang": true
},
{
"name": "awayekî din",
"langs": null
},
{
"name": "guharto",
"langs": null
},
{
"name": "hevmaneya",
"langs": null
},
{
"name": "kevnbûyî",
"langs": null
},
{
"name": "kêmpeyda",
"langs": null
},
{
"name": "şaşnivîs",
"langs": null
}
],
"jederDefs": {
"jêder-kitêb": {
"label": "Kitêb",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "sernav",
"label": "Sernav"
},
{
"key": "sernavê orjînal",
"label": "Sernavê orjînal"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "weşanxane",
"label": "Weşanxane"
},
{
"key": "cih",
"label": "Cih"
},
{
"key": "sal",
"label": "Sal"
},
{
"key": "çap",
"label": "Çap"
},
{
"key": "rûpel",
"label": "Rûpel"
},
{
"key": "isbn",
"label": "ISBN"
},
{
"key": "url",
"label": "URL"
}
]
},
"jêder-kovar": {
"label": "Kovar",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "nivîskar2",
"label": "Nivîskar 2"
},
{
"key": "sernav",
"label": "Sernav"
},
{
"key": "sernavê orjînal",
"label": "Sernavê orjînal"
},
{
"key": "kovar",
"label": "Kovar"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "weşanxane",
"label": "Weşanxane"
},
{
"key": "cild",
"label": "Cild"
},
{
"key": "hejmar",
"label": "Hejmar"
},
{
"key": "sal",
"label": "Sal"
},
{
"key": "rûpel",
"label": "Rûpel"
},
{
"key": "url",
"label": "URL"
}
]
},
"jêder-websîte": {
"label": "Websîte",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "sernav",
"label": "Sernav"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "websîte",
"label": "Websîte"
},
{
"key": "url",
"label": "URL"
},
{
"key": "arşîv",
"label": "Arşîva URLê"
},
{
"key": "sal",
"label": "Sal"
},
{
"key": "tarîx",
"label": "Tarîx"
}
]
}
},
"pronTpls": [
{
"name": "ku-IPA",
"desc": "IPAya otomatîk ji bo kurmancî (kontrol hewce ye)",
"snippet": "* {{ku-IPA}}",
"langs": [
"ku"
],
"noLang": true
},
{
"name": "ku-kîte",
"desc": "Hecekirina otomatîk ji bo kurmancî",
"snippet": "* {{ku-kîte}}",
"langs": [
"ku"
],
"noLang": true
},
{
"name": "zza-IPA",
"desc": "IPAya otomatîk ji bo zazakî",
"snippet": "* {{zza-IPA}}",
"langs": [
"zza"
],
"noLang": true
},
{
"name": "zza-kîte",
"desc": "Hecekirina otomatîk ji bo zazakî",
"snippet": "* {{zza-kîte}}",
"langs": [
"zza"
],
"noLang": true
},
{
"name": "IPA",
"desc": "Alfabeya fonetîk a navmiletî (IPA)",
"snippet": "* {{IPA|CODE|/…/}}"
},
{
"name": "bilêv",
"desc": "{{bilêv|herf|nerm, req|hejmar}}",
"snippet": "* {{bilêv|herf|cure|hejmar}}"
},
{
"name": "kîte",
"desc": "Şablona hecekirinê",
"snippet": "* {{kîte|CODE|he|ce}}"
},
{
"name": "hevdeng",
"desc": "Hevdeng — gotinên bi heman dengî",
"snippet": "* {{hevdeng|CODE|…}}"
}
],
"etymTpls": [
{
"name": "ji",
"desc": "Ji zimanekî din hatiye — {{ji|en|ku|anatomy|tr=|t=}}",
"snippet": "{{ji|SOURCELANG|CODE|…}}"
},
{
"name": "deyn",
"desc": "Deynkirî — {{deyn|lang|kod|kelîme}}",
"snippet": "{{deyn|SOURCELANG|CODE|…}}"
},
{
"name": "mîr",
"desc": "Mîrasmayî — {{mîr|lang|kod|kelîme}}",
"snippet": "{{mîr|SOURCELANG|CODE|…}}"
},
{
"name": "qertaf",
"desc": "Ji bi pêşgir, paşgir û hevedudanî — {{qertaf|kod|pêşgir-|reh}}",
"snippet": "{{qertaf|CODE|…|…}}"
},
{
"name": "q",
"desc": "Referans bo rûpelekê — {{q|kod|kelîme}} navê zimanî dernakeve",
"snippet": "{{q|CODE|…}}"
},
{
"name": "hv",
"desc": "Hevreh — {{hv|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{hv|SOURCELANG|…}}"
},
{
"name": "nh",
"desc": "Nehevreh — {{nh|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{nh|SOURCELANG|…}}"
},
{
"name": "cotreh",
"desc": "Cotreh — teror {{cotreh|ku|tirs}}",
"snippet": "{{cotreh|CODE|…}}"
},
{
"name": "yekser",
"desc": "tercimeya yekser — {{yekser|lang|kod|kelîme}}",
"snippet": "{{yekser|SOURCELANG|CODE|…}}"
},
{
"name": "wergirtin",
"desc": "Wergirtina maneyê — {{wergirtin|lang|kod|kelîme}}",
"snippet": "{{wergirtin|SOURCELANG|CODE|…}}"
},
{
"name": "paşvesazî",
"desc": "Paşvesazî — greed {{paşvesazî|en|greedy}}",
"snippet": "{{paşvesazî|CODE|…}}"
},
{
"name": "qusandin",
"desc": "Qusandin — Miho {{qusandin|ku|Mihemmed}}",
"snippet": "{{qusandin|CODE|…}}"
},
{
"name": "têkilkirin",
"desc": "Têkilkirin — sormancî {{têkilkirin|ku|soranî|kurmancî}}",
"snippet": "{{têkilkirin|CODE|…}}"
},
{
"name": "nyz",
"desc": "Nayê zanîn — {{nyz|ku|hûr=1}}",
"snippet": "{{nyz|CODE}}"
},
{
"name": "onom",
"desc": "Onomatopoetîk — {{onom|ku|hûr=1}}",
"snippet": "{{onom|CODE}}"
},
{
"name": "navmiletî",
"desc": "Navmiletî — {{navmiletî|ku|hûr=1}}",
"snippet": "{{navmiletî|CODE}}"
},
{
"name": "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",
"mn",
"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"
}
}
}
ikqomxr80fj3nzidvn0fgxscm4cpbl7
6291583
6291579
2026-07-26T17:31:10Z
Wikihez
7344
Restored revision 6276921 by [[Special:Contributions/Wikihez|Wikihez]] ([[User talk:Wikihez|talk]]) ([[:m:User:Xiplus/TwinkleGlobal|TwinkleGlobal]])
6291583
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ê û nêr",
"mnmane": "mê yan nêr li gorî maneyê",
"g": "giştî",
"y": "yekjimar",
"p": "pirjimar"
},
"tplDefs": [
{
"name": "awayê Jacobsonê",
"langs": [
"zza"
]
},
{
"name": "rastnivîs",
"langs": [
"ku"
],
"noLang": true
},
{
"name": "ku-Latn",
"langs": [
"ku"
],
"noLang": true
},
{
"name": "sdh-Arab",
"langs": [
"sdh"
],
"noLang": true
},
{
"name": "hac-Arab",
"langs": [
"hac"
],
"noLang": true
},
{
"name": "ja-romanîzekirin",
"langs": [
"ja"
],
"noLang": true
},
{
"name": "ckb-Arab",
"langs": [
"ckb"
],
"noLang": true
},
{
"name": "awayekî din",
"langs": null
},
{
"name": "guharto",
"langs": null
},
{
"name": "hevmaneya",
"langs": null
},
{
"name": "kevnbûyî",
"langs": null
},
{
"name": "kêmpeyda",
"langs": null
},
{
"name": "şaşnivîs",
"langs": null
}
],
"jederDefs": {
"jêder-kitêb": {
"label": "Kitêb",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "sernav",
"label": "Sernav"
},
{
"key": "sernavê orjînal",
"label": "Sernavê orjînal"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "weşanxane",
"label": "Weşanxane"
},
{
"key": "cih",
"label": "Cih"
},
{
"key": "sal",
"label": "Sal"
},
{
"key": "çap",
"label": "Çap"
},
{
"key": "rûpel",
"label": "Rûpel"
},
{
"key": "isbn",
"label": "ISBN"
},
{
"key": "url",
"label": "URL"
}
]
},
"jêder-kovar": {
"label": "Kovar",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "nivîskar2",
"label": "Nivîskar 2"
},
{
"key": "sernav",
"label": "Sernav"
},
{
"key": "sernavê orjînal",
"label": "Sernavê orjînal"
},
{
"key": "kovar",
"label": "Kovar"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "weşanxane",
"label": "Weşanxane"
},
{
"key": "cild",
"label": "Cild"
},
{
"key": "hejmar",
"label": "Hejmar"
},
{
"key": "sal",
"label": "Sal"
},
{
"key": "rûpel",
"label": "Rûpel"
},
{
"key": "url",
"label": "URL"
}
]
},
"jêder-websîte": {
"label": "Websîte",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "sernav",
"label": "Sernav"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "websîte",
"label": "Websîte"
},
{
"key": "url",
"label": "URL"
},
{
"key": "arşîv",
"label": "Arşîva URLê"
},
{
"key": "sal",
"label": "Sal"
},
{
"key": "tarîx",
"label": "Tarîx"
}
]
}
},
"pronTpls": [
{
"name": "ku-IPA",
"desc": "IPAya otomatîk ji bo kurmancî (kontrol hewce ye)",
"snippet": "* {{ku-IPA}}",
"langs": [
"ku"
],
"noLang": true
},
{
"name": "ku-kîte",
"desc": "Hecekirina otomatîk ji bo kurmancî",
"snippet": "* {{ku-kîte}}",
"langs": [
"ku"
],
"noLang": true
},
{
"name": "zza-IPA",
"desc": "IPAya otomatîk ji bo zazakî",
"snippet": "* {{zza-IPA}}",
"langs": [
"zza"
],
"noLang": true
},
{
"name": "zza-kîte",
"desc": "Hecekirina otomatîk ji bo zazakî",
"snippet": "* {{zza-kîte}}",
"langs": [
"zza"
],
"noLang": true
},
{
"name": "IPA",
"desc": "Alfabeya fonetîk a navmiletî (IPA)",
"snippet": "* {{IPA|CODE|/…/}}"
},
{
"name": "bilêv",
"desc": "{{bilêv|herf|nerm, req|hejmar}}",
"snippet": "* {{bilêv|herf|cure|hejmar}}"
},
{
"name": "kîte",
"desc": "Şablona hecekirinê",
"snippet": "* {{kîte|CODE|he|ce}}"
},
{
"name": "hevdeng",
"desc": "Hevdeng — gotinên bi heman dengî",
"snippet": "* {{hevdeng|CODE|…}}"
}
],
"etymTpls": [
{
"name": "ji",
"desc": "Ji zimanekî din hatiye — {{ji|en|ku|anatomy|tr=|t=}}",
"snippet": "{{ji|SOURCELANG|CODE|…}}"
},
{
"name": "deyn",
"desc": "Deynkirî — {{deyn|lang|kod|kelîme}}",
"snippet": "{{deyn|SOURCELANG|CODE|…}}"
},
{
"name": "mîr",
"desc": "Mîrasmayî — {{mîr|lang|kod|kelîme}}",
"snippet": "{{mîr|SOURCELANG|CODE|…}}"
},
{
"name": "qertaf",
"desc": "Ji bi pêşgir, paşgir û hevedudanî — {{qertaf|kod|pêşgir-|reh}}",
"snippet": "{{qertaf|CODE|…|…}}"
},
{
"name": "q",
"desc": "Referans bo rûpelekê — {{q|kod|kelîme}} navê zimanî dernakeve",
"snippet": "{{q|CODE|…}}"
},
{
"name": "hv",
"desc": "Hevreh — {{hv|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{hv|SOURCELANG|…}}"
},
{
"name": "nh",
"desc": "Nehevreh — {{nh|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{nh|SOURCELANG|…}}"
},
{
"name": "cotreh",
"desc": "Cotreh — teror {{cotreh|ku|tirs}}",
"snippet": "{{cotreh|CODE|…}}"
},
{
"name": "yekser",
"desc": "tercimeya yekser — {{yekser|lang|kod|kelîme}}",
"snippet": "{{yekser|SOURCELANG|CODE|…}}"
},
{
"name": "wergirtin",
"desc": "Wergirtina maneyê — {{wergirtin|lang|kod|kelîme}}",
"snippet": "{{wergirtin|SOURCELANG|CODE|…}}"
},
{
"name": "paşvesazî",
"desc": "Paşvesazî — greed {{paşvesazî|en|greedy}}",
"snippet": "{{paşvesazî|CODE|…}}"
},
{
"name": "qusandin",
"desc": "Qusandin — Miho {{qusandin|ku|Mihemmed}}",
"snippet": "{{qusandin|CODE|…}}"
},
{
"name": "têkilkirin",
"desc": "Têkilkirin — sormancî {{têkilkirin|ku|soranî|kurmancî}}",
"snippet": "{{têkilkirin|CODE|…}}"
},
{
"name": "nyz",
"desc": "Nayê zanîn — {{nyz|ku|hûr=1}}",
"snippet": "{{nyz|CODE}}"
},
{
"name": "onom",
"desc": "Onomatopoetîk — {{onom|ku|hûr=1}}",
"snippet": "{{onom|CODE}}"
},
{
"name": "navmiletî",
"desc": "Navmiletî — {{navmiletî|ku|hûr=1}}",
"snippet": "{{navmiletî|CODE}}"
},
{
"name": "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",
"mn",
"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"
}
}
}
26wzz3z4txlu6946jit59b7kmjiyso5
6291584
6291583
2026-07-26T17:35:10Z
Wikihez
7344
6291584
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ê û nêr",
"mnmane": "mê yan nêr li gorî maneyê",
"g": "giştî",
"y": "yekjimar",
"p": "pirjimar"
},
"tplDefs": [
{
"name": "awayê Jacobsonê",
"langs": [
"zza"
]
},
{
"name": "rastnivîs",
"langs": [
"ku"
],
"noLang": true
},
{
"name": "ku-Latn",
"langs": [
"ku"
],
"noLang": true
},
{
"name": "sdh-Arab",
"langs": [
"sdh"
],
"noLang": true
},
{
"name": "hac-Arab",
"langs": [
"hac"
],
"noLang": true
},
{
"name": "ja-romanîzekirin",
"langs": [
"ja"
],
"noLang": true
},
{
"name": "ckb-Arab",
"langs": [
"ckb"
],
"noLang": true
},
{
"name": "awayekî din",
"langs": null
},
{
"name": "guharto",
"langs": null
},
{
"name": "hevmaneya",
"langs": null
},
{
"name": "kevnbûyî",
"langs": null
},
{
"name": "kêmpeyda",
"langs": null
},
{
"name": "şaşnivîs",
"langs": null
}
],
"jederDefs": {
"jêder-kitêb": {
"label": "Kitêb",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "sernav",
"label": "Sernav"
},
{
"key": "sernavê orjînal",
"label": "Sernavê orjînal"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "weşanxane",
"label": "Weşanxane"
},
{
"key": "cih",
"label": "Cih"
},
{
"key": "sal",
"label": "Sal"
},
{
"key": "çap",
"label": "Çap"
},
{
"key": "rûpel",
"label": "Rûpel"
},
{
"key": "isbn",
"label": "ISBN"
},
{
"key": "url",
"label": "URL"
}
]
},
"jêder-kovar": {
"label": "Kovar",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "nivîskar2",
"label": "Nivîskar 2"
},
{
"key": "sernav",
"label": "Sernav"
},
{
"key": "sernavê orjînal",
"label": "Sernavê orjînal"
},
{
"key": "kovar",
"label": "Kovar"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "weşanxane",
"label": "Weşanxane"
},
{
"key": "cild",
"label": "Cild"
},
{
"key": "hejmar",
"label": "Hejmar"
},
{
"key": "sal",
"label": "Sal"
},
{
"key": "rûpel",
"label": "Rûpel"
},
{
"key": "url",
"label": "URL"
}
]
},
"jêder-websîte": {
"label": "Websîte",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "sernav",
"label": "Sernav"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "websîte",
"label": "Websîte"
},
{
"key": "url",
"label": "URL"
},
{
"key": "arşîv",
"label": "Arşîva URLê"
},
{
"key": "sal",
"label": "Sal"
},
{
"key": "tarîx",
"label": "Tarîx"
}
]
}
},
"pronTpls": [
{
"name": "ku-IPA",
"desc": "IPAya otomatîk ji bo kurmancî (kontrol hewce ye)",
"snippet": "* {{ku-IPA}}",
"langs": [
"ku"
],
"noLang": true
},
{
"name": "ku-kîte",
"desc": "Hecekirina otomatîk ji bo kurmancî",
"snippet": "* {{ku-kîte}}",
"langs": [
"ku"
],
"noLang": true
},
{
"name": "zza-IPA",
"desc": "IPAya otomatîk ji bo zazakî",
"snippet": "* {{zza-IPA}}",
"langs": [
"zza"
],
"noLang": true
},
{
"name": "zza-kîte",
"desc": "Hecekirina otomatîk ji bo zazakî",
"snippet": "* {{zza-kîte}}",
"langs": [
"zza"
],
"noLang": true
},
{
"name": "IPA",
"desc": "Alfabeya fonetîk a navmiletî (IPA)",
"snippet": "* {{IPA|CODE|/…/}}"
},
{
"name": "bilêv",
"desc": "{{bilêv|herf|nerm, req|hejmar}}",
"snippet": "* {{bilêv|herf|cure|hejmar}}"
},
{
"name": "kîte",
"desc": "Şablona hecekirinê",
"snippet": "* {{kîte|CODE|he|ce}}"
},
{
"name": "hevdeng",
"desc": "Hevdeng — gotinên bi heman dengî",
"snippet": "* {{hevdeng|CODE|…}}"
}
],
"etymTpls": [
{
"name": "ji",
"desc": "Ji zimanekî din hatiye — {{ji|en|ku|anatomy|tr=|t=}}",
"snippet": "{{ji|SOURCELANG|CODE|…}}"
},
{
"name": "deyn",
"desc": "Deynkirî — {{deyn|lang|kod|kelîme}}",
"snippet": "{{deyn|SOURCELANG|CODE|…}}"
},
{
"name": "mîr",
"desc": "Mîrasmayî — {{mîr|lang|kod|kelîme}}",
"snippet": "{{mîr|SOURCELANG|CODE|…}}"
},
{
"name": "qertaf",
"desc": "Ji bi pêşgir, paşgir û hevedudanî — {{qertaf|kod|pêşgir-|reh}}",
"snippet": "{{qertaf|CODE|…|…}}"
},
{
"name": "q",
"desc": "Referans bo rûpelekê — {{q|kod|kelîme}} navê zimanî dernakeve",
"snippet": "{{q|CODE|…}}"
},
{
"name": "hv",
"desc": "Hevreh — {{hv|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{hv|SOURCELANG|…}}"
},
{
"name": "nh",
"desc": "Nehevreh — {{nh|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{nh|SOURCELANG|…}}"
},
{
"name": "cotreh",
"desc": "Cotreh — teror {{cotreh|ku|tirs}}",
"snippet": "{{cotreh|CODE|…}}"
},
{
"name": "yekser",
"desc": "tercimeya yekser — {{yekser|lang|kod|kelîme}}",
"snippet": "{{yekser|SOURCELANG|CODE|…}}"
},
{
"name": "wergirtin",
"desc": "Wergirtina maneyê — {{wergirtin|lang|kod|kelîme}}",
"snippet": "{{wergirtin|SOURCELANG|CODE|…}}"
},
{
"name": "paşvesazî",
"desc": "Paşvesazî — greed {{paşvesazî|en|greedy}}",
"snippet": "{{paşvesazî|CODE|…}}"
},
{
"name": "qusandin",
"desc": "Qusandin — Miho {{qusandin|ku|Mihemmed}}",
"snippet": "{{qusandin|CODE|…}}"
},
{
"name": "têkilkirin",
"desc": "Têkilkirin — sormancî {{têkilkirin|ku|soranî|kurmancî}}",
"snippet": "{{têkilkirin|CODE|…}}"
},
{
"name": "nyz",
"desc": "Nayê zanîn — {{nyz|ku|hûr=1}}",
"snippet": "{{nyz|CODE}}"
},
{
"name": "onom",
"desc": "Onomatopoetîk — {{onom|ku|hûr=1}}",
"snippet": "{{onom|CODE}}"
},
{
"name": "navmiletî",
"desc": "Navmiletî — {{navmiletî|ku|hûr=1}}",
"snippet": "{{navmiletî|CODE}}"
},
{
"name": "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",
"mn",
"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"
}
}
}
kfifx2ehwhh4h5yzcarsyl0cbdqiaw9
6291586
6291584
2026-07-26T18:03:20Z
Wikihez
7344
6291586
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ê û nêr",
"mnmane": "mê yan nêr li gorî maneyê",
"g": "giştî",
"y": "yekjimar",
"p": "pirjimar"
},
"tplDefs": {
"awayê Jacobsonê": {
"langs": [
"zza"
]
},
"rastnivîs": {
"langs": [
"ku"
],
"noLang": true
},
"ku-Latn": {
"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
},
"awayekî din": {
"langs": null
},
"guharto": {
"langs": null
},
"hevmaneya": {
"langs": null
},
"kevnbûyî": {
"langs": null
},
"kêmpeyda": {
"langs": null
},
"şaşnivîs": {
"langs": null
}
},
"jederDefs": {
"jêder-kitêb": {
"label": "Kitêb",
"fields": {
"nivîskar": "Nivîskar",
"sernav": "Sernav",
"sernavê orjînal": "Sernavê orjînal",
"terciman": "Terciman",
"weşanxane": "Weşanxane",
"cih": "Cih",
"sal": "Sal",
"çap": "Çap",
"rûpel": "Rûpel",
"isbn": "ISBN",
"url": "URL"
}
},
"jêder-kovar": {
"label": "Kovar",
"fields": {
"nivîskar": "Nivîskar",
"nivîskar2": "Nivîskar 2",
"sernav": "Sernav",
"sernavê orjînal": "Sernavê orjînal",
"kovar": "Kovar",
"terciman": "Terciman",
"weşanxane": "Weşanxane",
"cild": "Cild",
"hejmar": "Hejmar",
"sal": "Sal",
"rûpel": "Rûpel",
"url": "URL"
}
},
"jêder-websîte": {
"label": "Websîte",
"fields": {
"nivîskar": "Nivîskar",
"sernav": "Sernav",
"terciman": "Terciman",
"websîte": "Websîte",
"url": "URL",
"arşîv": "Arşîva URLê",
"sal": "Sal",
"tarîx": "Tarîx"
}
}
},
"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|/…/}}"
},
"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",
"mn",
"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"
}
}
}
j9i77gp3d50wh8e6rtma0tb8knfsavo
Bikarhêner:Wikihez/skrîpt/js/Gadget-citoid-helper.js
2
1357853
6291573
6265090
2026-07-26T15:54:43Z
Wikihez
7344
6291573
javascript
text/javascript
/**
* Gadget-citoid-helper.js
*
* Amûreke hevpar ji bo standina daneyên çavkaniyê ji Citoid û
* veguherandina wan bo qadên şablonên jêder-* (jêder-kitêb, jêder-kovar, jêder-websîte).
*
* BIKARANÎN JI SCRIPTEKE DIN:
*
* 1) Riya herî baş: vê rûpelê wek dependency lê zêde bike di eyarên
* gadgeta xwe de li ser [[Special:Gadgets]] (an jî MediaWiki:Gadgets-definition),
* paşê:
*
* mw.loader.using("ext.gadget.citoid-helper").then(function () {
* wikt.citoid.fetchJederFields(url).then(function (result) {
* console.log(result.jederKey, result.fields);
* });
* });
*
* 2) An jî, heke ne mimkun be gadget bi vî awayî were qeydkirin, fonksiyona
* alîkar a `wikt.citoidHelper.ensure()` a li jêr bikar bîne, ku bixweber
* vê rûpelê bar dike heke jixwe nehatibe barkirin:
*
* wikt.citoidHelper.ensure().then(function () {
* wikt.citoid.fetchJederFields(url).then(...);
* });
*
* API ya `window.wikt.citoid`:
* - getRestUrl() → {string} URLya REST a Citoid a vê wîkiyê
* - yearFromDate(dateStr) → {string} sala (4 reqem) ji rêzikeke tarîxê
* - formatAuthors(authors) → {string} navên nivîskaran wek rêzikek yekane
* - typeToJederKey(itemType) → {string} "jêder-kitêb" / "jêder-kovar" / "jêder-websîte"
* - dataToJederFields(jederKey, data) → {Object<string,string>} qonaxên şablona jêder-*
* - fetchData(query) → {jQuery.Promise<Object>} daneya xam a Citoid
* - fetchJederFields(query) → {jQuery.Promise<{jederKey, fields, raw}>} hêsankirî
* - TYPE_TO_JEDER → {Object} xerîteya itemType → jederKey
*/
window.wikt = window.wikt || {};
(function () {
"use strict";
// Heke jixwe hatibe barkirin, dubare neke.
if (window.wikt.citoid) {
return;
}
// Xerîteya cureyên Citoid (itemType) li ser şablonên me yên jêder-*
var CITOID_TYPE_TO_JEDER = {
"book": "jêder-kitêb",
"bookSection": "jêder-kitêb",
"thesis": "jêder-kitêb",
"report": "jêder-kitêb",
"manuscript": "jêder-kitêb",
"journalArticle": "jêder-kovar",
"magazineArticle": "jêder-kovar",
"newspaperArticle": "jêder-kovar",
"webpage": "jêder-websîte",
"blogPost": "jêder-websîte",
"forumPost": "jêder-websîte",
"videoRecording": "jêder-websîte",
"podcast": "jêder-websîte",
"encyclopediaArticle": "jêder-websîte"
};
/**
* Ji mantiqa [[:en:w:User:BrandonXLF/Citoid.js]]
* Vedigere URLya REST a Citoid, li gorî eyarên vê wîkiyê.
* @return {string}
*/
function getRestUrl() {
var cfg = mw.config.get("wgCitoidConfig");
var vecfg = mw.config.get("wgVisualEditorConfig");
return (cfg && (cfg.fullRestbaseUrl || cfg.wbFullRestbaseUrl)) ? (cfg.fullRestbaseUrl || cfg.wbFullRestbaseUrl) + "v1/data/citation/mediawiki/"
: (vecfg && vecfg.fullRestbaseUrl) ? vecfg.fullRestbaseUrl + "v1/data/citation/mediawiki/"
: (cfg && cfg.citoidServiceUrl) ? cfg.citoidServiceUrl + "/mediawiki/"
: "https://ku.wiktionary.org/api/rest_v1/data/citation/mediawiki/";
}
/**
* Ji tarîxa Citoid (rêzik an tarîxeke ISO) sala wê (4 reqem) derdixe.
* @param dateStr {string}
* @return {string}
*/
function yearFromDate(dateStr) {
if (!dateStr) {
return "";
}
var match = /\d{4}/.exec(dateStr);
return match ? match[0] : "";
}
/**
* Navên nivîskaran ên Citoid (rêzek [pêşnav, paşnav]) dike rêzikek yekane.
* @param authors {Array<Array<string>>}
* @return {string}
*/
function formatAuthors(authors) {
if (!Array.isArray(authors)) {
return "";
}
return authors.map(function (pair) {
return (pair || []).filter(function (part) {
return !!part;
}).join(" ");
}).filter(function (name) {
return !!name;
}).join("; ");
}
function formatEditors(editors) {
if (!Array.isArray(editors)) {
return "";
}
return editors.map(function (pair) {
return (pair || []).filter(function (part) {
return !!part;
}).join(" ");
}).filter(function (name) {
return !!name;
}).join("; ");
}
/**
* Li gorî itemType a Citoid, navê şablona jêder-* a munasi vedigerîne.
* @param itemType {string}
* @return {string} Yek ji "jêder-kitêb", "jêder-kovar" an "jêder-websîte".
*/
function typeToJederKey(itemType) {
return CITOID_TYPE_TO_JEDER[itemType] || "jêder-websîte";
}
/**
* Daneyên Citoid vediguherîne tabloyeke parametreyên şablona jêder-* (li gorî cureyê wê).
* @param jederKey {string} Yek ji "jêder-kitêb", "jêder-kovar" an "jêder-websîte".
* @param data {Object} Netîceya Citoid.
* @return {Object<string, string>}
*/
function dataToJederFields(jederKey, data) {
var out = {};
var author = formatAuthors(data.author);
var editor = formatEditors(data.editor);
if (author) { out["nivîskar"] = author; }
if (editor) { out["amadekar"] = editor; }
if (data.title) { out["sernav"] = data.title; }
if (data.url) { out.url = data.url; }
if (jederKey === "jêder-kitêb") {
if (data.publisher) { out["weşanxane"] = data.publisher; }
if (data.place) { out.cih = data.place; }
if (data.date) { out.sal = yearFromDate(data.date); }
if (data.ISBN) { out.isbn = Array.isArray(data.ISBN) ? data.ISBN[0] : data.ISBN; }
if (data.edition) { out["çap"] = data.edition; }
if (data.pages) { out["rûpel"] = data.pages; }
} else if (jederKey === "jêder-kovar") {
if (data.publicationTitle) { out.kovar = data.publicationTitle; }
if (data.publisher) { out["weşanxane"] = data.publisher; }
if (data.volume) { out.cild = data.volume; }
if (data.issue) { out.hejmar = data.issue; }
if (data.pages) { out["rûpel"] = data.pages; }
if (data.date) { out.sal = yearFromDate(data.date); }
} else if (jederKey === "jêder-websîte") {
if (data.websiteTitle) { out["websîte"] = data.websiteTitle; }
if (data.date) {
out["tarîx"] = data.date;
}
}
return out;
}
/**
* Ji Citoid daneyên çavkaniyekê tîne (li gorî URL, DOI an ISBNyê).
* @param query {string}
* @return {jQuery.Promise} Bi daneya ewil a Citoid (Object) tê safîkirin.
*/
function fetchData(query) {
var url;
try {
url = encodeURIComponent(decodeURIComponent(query));
} catch (e) {
url = encodeURIComponent(query);
}
return $.get(getRestUrl() + url).then(function (res) {
if (!res || !res[0]) {
return $.Deferred().reject("Netîceya ji Citoidê vala ye.").promise();
}
return res[0];
});
}
/**
* Fonksiyoneke hêsankirî: ji URL/DOI/ISBNyekê rasterast şablona jêder-* a munasib
* û qonaxên wê yên tijîkirî vedigerîne (fetchData + typeToJederKey + dataToJederFields
* bi hev re).
* @param query {string}
* @return {jQuery.Promise} Bi {jederKey: string, fields: Object<string,string>, raw: Object} tê safîkirin.
*/
function fetchJederFields(query) {
return fetchData(query).then(function (data) {
var jederKey = typeToJederKey(data.itemType);
return {
jederKey: jederKey,
fields: dataToJederFields(jederKey, data),
raw: data
};
});
}
window.wikt.citoid = {
getRestUrl: getRestUrl,
yearFromDate: yearFromDate,
formatAuthors: formatAuthors,
typeToJederKey: typeToJederKey,
dataToJederFields: dataToJederFields,
fetchData: fetchData,
fetchJederFields: fetchJederFields,
TYPE_TO_JEDER: CITOID_TYPE_TO_JEDER
};
/**
* Alîkar ji bo scriptên din: heke wikt.citoid hîn nehatibe barkirin,
* vê rûpelê bixweber bar dike û pey re "resolve" dibe.
* Bikaranîn: wikt.citoidHelper.ensure().then(function() { ... wikt.citoid ... });
*/
window.wikt.citoidHelper = {
ensure: function () {
if (window.wikt.citoid) {
return $.Deferred().resolve().promise();
}
var url = mw.util.getUrl("MediaWiki:Gadget-citoid-helper.js", {
action: "raw",
ctype: "text/javascript"
});
return mw.loader.getScript(mw.config.get("wgServer") + url);
}
};
})();
i1a3i5jf7t29mnd9m9vurpt3e5ha0jt
6291574
6291573
2026-07-26T15:57:25Z
Wikihez
7344
6291574
javascript
text/javascript
/**
* Gadget-citoid-helper.js
*
* Amûreke hevpar ji bo standina daneyên çavkaniyê ji Citoid û
* veguherandina wan bo qadên şablonên jêder-* (jêder-kitêb, jêder-kovar, jêder-websîte).
*
* BIKARANÎN JI SCRIPTEKE DIN:
*
* 1) Riya herî baş: vê rûpelê wek dependency lê zêde bike di eyarên
* gadgeta xwe de li ser [[Special:Gadgets]] (an jî MediaWiki:Gadgets-definition),
* paşê:
*
* mw.loader.using("ext.gadget.citoid-helper").then(function () {
* wikt.citoid.fetchJederFields(url).then(function (result) {
* console.log(result.jederKey, result.fields);
* });
* });
*
* 2) An jî, heke ne mimkun be gadget bi vî awayî were qeydkirin, fonksiyona
* alîkar a `wikt.citoidHelper.ensure()` a li jêr bikar bîne, ku bixweber
* vê rûpelê bar dike heke jixwe nehatibe barkirin:
*
* wikt.citoidHelper.ensure().then(function () {
* wikt.citoid.fetchJederFields(url).then(...);
* });
*
* API ya `window.wikt.citoid`:
* - getRestUrl() → {string} URLya REST a Citoid a vê wîkiyê
* - yearFromDate(dateStr) → {string} sala (4 reqem) ji rêzikeke tarîxê
* - formatAuthors(authors) → {string} navên nivîskaran wek rêzikek yekane
* - typeToJederKey(itemType) → {string} "jêder-kitêb" / "jêder-kovar" / "jêder-websîte"
* - dataToJederFields(jederKey, data) → {Object<string,string>} merheleyên şablona jêder-*
* - fetchData(query) → {jQuery.Promise<Object>} daneya xam a Citoid
* - fetchJederFields(query) → {jQuery.Promise<{jederKey, fields, raw}>} hêsankirî
* - TYPE_TO_JEDER → {Object} xerîteya itemType → jederKey
*/
window.wikt = window.wikt || {};
(function () {
"use strict";
// Heke jixwe hatibe barkirin, dubare neke.
if (window.wikt.citoid) {
return;
}
// Xerîteya cureyên Citoid (itemType) li ser şablonên me yên jêder-*
var CITOID_TYPE_TO_JEDER = {
"book": "jêder-kitêb",
"bookSection": "jêder-kitêb",
"thesis": "jêder-kitêb",
"report": "jêder-kitêb",
"manuscript": "jêder-kitêb",
"journalArticle": "jêder-kovar",
"magazineArticle": "jêder-kovar",
"newspaperArticle": "jêder-kovar",
"webpage": "jêder-websîte",
"blogPost": "jêder-websîte",
"forumPost": "jêder-websîte",
"videoRecording": "jêder-websîte",
"podcast": "jêder-websîte",
"encyclopediaArticle": "jêder-websîte"
};
/**
* Ji mantiqa [[:en:w:User:BrandonXLF/Citoid.js]]
* Vedigere URLya REST a Citoid, li gorî eyarên vê wîkiyê.
* @return {string}
*/
function getRestUrl() {
var cfg = mw.config.get("wgCitoidConfig");
var vecfg = mw.config.get("wgVisualEditorConfig");
return (cfg && (cfg.fullRestbaseUrl || cfg.wbFullRestbaseUrl)) ? (cfg.fullRestbaseUrl || cfg.wbFullRestbaseUrl) + "v1/data/citation/mediawiki/"
: (vecfg && vecfg.fullRestbaseUrl) ? vecfg.fullRestbaseUrl + "v1/data/citation/mediawiki/"
: (cfg && cfg.citoidServiceUrl) ? cfg.citoidServiceUrl + "/mediawiki/"
: "https://ku.wiktionary.org/api/rest_v1/data/citation/mediawiki/";
}
/**
* Ji tarîxa Citoid (rêzik an tarîxeke ISO) sala wê (4 reqem) derdixe.
* @param dateStr {string}
* @return {string}
*/
function yearFromDate(dateStr) {
if (!dateStr) {
return "";
}
var match = /\d{4}/.exec(dateStr);
return match ? match[0] : "";
}
/**
* Navên nivîskaran ên Citoid (rêzek [pêşnav, paşnav]) dike rêzikek yekane.
* @param authors {Array<Array<string>>}
* @return {string}
*/
function formatAuthors(authors) {
if (!Array.isArray(authors)) {
return "";
}
return authors.map(function (pair) {
return (pair || []).filter(function (part) {
return !!part;
}).join(" ");
}).filter(function (name) {
return !!name;
}).join("; ");
}
function formatEditors(editors) {
if (!Array.isArray(editors)) {
return "";
}
return editors.map(function (pair) {
return (pair || []).filter(function (part) {
return !!part;
}).join(" ");
}).filter(function (name) {
return !!name;
}).join("; ");
}
/**
* Li gorî itemType a Citoid, navê şablona jêder-* a munasib vedigerîne.
* @param itemType {string}
* @return {string} Yek ji "jêder-kitêb", "jêder-kovar" an "jêder-websîte".
*/
function typeToJederKey(itemType) {
return CITOID_TYPE_TO_JEDER[itemType] || "jêder-websîte";
}
/**
* Daneyên Citoid vediguherîne tabloyeke parametreyên şablona jêder-* (li gorî cureyê wê).
* @param jederKey {string} Yek ji "jêder-kitêb", "jêder-kovar" an "jêder-websîte".
* @param data {Object} Netîceya Citoid.
* @return {Object<string, string>}
*/
function dataToJederFields(jederKey, data) {
var out = {};
var author = formatAuthors(data.author);
var editor = formatEditors(data.editor);
if (author) { out["nivîskar"] = author; }
if (editor) { out["amadekar"] = editor; }
if (data.title) { out["sernav"] = data.title; }
if (data.url) { out.url = data.url; }
if (jederKey === "jêder-kitêb") {
if (data.publisher) { out["weşanxane"] = data.publisher; }
if (data.place) { out.cih = data.place; }
if (data.date) { out.sal = yearFromDate(data.date); }
if (data.ISBN) { out.isbn = Array.isArray(data.ISBN) ? data.ISBN[0] : data.ISBN; }
if (data.edition) { out["çap"] = data.edition; }
if (data.pages) { out["rûpel"] = data.pages; }
} else if (jederKey === "jêder-kovar") {
if (data.publicationTitle) { out.kovar = data.publicationTitle; }
if (data.publisher) { out["weşanxane"] = data.publisher; }
if (data.volume) { out.cild = data.volume; }
if (data.issue) { out.hejmar = data.issue; }
if (data.pages) { out["rûpel"] = data.pages; }
if (data.date) { out.sal = yearFromDate(data.date); }
} else if (jederKey === "jêder-websîte") {
if (data.websiteTitle) { out["websîte"] = data.websiteTitle; }
if (data.date) {
out["tarîx"] = data.date;
}
}
return out;
}
/**
* Ji Citoid daneyên çavkaniyekê tîne (li gorî URL, DOI an ISBNyê).
* @param query {string}
* @return {jQuery.Promise} Bi daneya ewil a Citoid (Object) tê safîkirin.
*/
function fetchData(query) {
var url;
try {
url = encodeURIComponent(decodeURIComponent(query));
} catch (e) {
url = encodeURIComponent(query);
}
return $.get(getRestUrl() + url).then(function (res) {
if (!res || !res[0]) {
return $.Deferred().reject("Netîceya ji Citoidê vala ye.").promise();
}
return res[0];
});
}
/**
* Fonksiyoneke hêsankirî: ji URL/DOI/ISBNyekê rasterast şablona jêder-* a munasib
* û merheleyên wê yên tijîkirî vedigerîne (fetchData + typeToJederKey + dataToJederFields
* bi hev re).
* @param query {string}
* @return {jQuery.Promise} Bi {jederKey: string, fields: Object<string,string>, raw: Object} tê safîkirin.
*/
function fetchJederFields(query) {
return fetchData(query).then(function (data) {
var jederKey = typeToJederKey(data.itemType);
return {
jederKey: jederKey,
fields: dataToJederFields(jederKey, data),
raw: data
};
});
}
window.wikt.citoid = {
getRestUrl: getRestUrl,
yearFromDate: yearFromDate,
formatAuthors: formatAuthors,
typeToJederKey: typeToJederKey,
dataToJederFields: dataToJederFields,
fetchData: fetchData,
fetchJederFields: fetchJederFields,
TYPE_TO_JEDER: CITOID_TYPE_TO_JEDER
};
/**
* Alîkar ji bo scriptên din: heke wikt.citoid hîn nehatibe barkirin,
* vê rûpelê bixweber bar dike û pey re "resolve" dibe.
* Bikaranîn: wikt.citoidHelper.ensure().then(function() { ... wikt.citoid ... });
*/
window.wikt.citoidHelper = {
ensure: function () {
if (window.wikt.citoid) {
return $.Deferred().resolve().promise();
}
var url = mw.util.getUrl("MediaWiki:Gadget-citoid-helper.js", {
action: "raw",
ctype: "text/javascript"
});
return mw.loader.getScript(mw.config.get("wgServer") + url);
}
};
})();
hzym8pr8qmqgre968ao6new00hqiz5a
Wîkîferheng:Xebat
4
1357857
6291575
6290823
2026-07-26T15:58:53Z
Wikihez
7344
/* */ bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re
6291575
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)
= 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)
== [[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.js/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)
== [[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)
== 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)
== Gadget-linkify ==
* [[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)
== 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:
<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.
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)
= Moda tarî =
== MediaWiki:Newarticletext ==
* [[MediaWiki:Newarticletext]]: Divê rengên wê were rastkirin.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Modul:dara kategoriyan ==
* [[Modul:dara kategoriyan]] link_box —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== mane? ==
* {{ş|mane?}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== bêçavkanî ==
* {{ş|bêçavkanî}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== de-tew-nerm-ern ==
* {{ş|de-tew-nerm-ern}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 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)
== 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)
== [[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)
hko2we2yrodp8zjeeexikrzexzber6l
6291576
6291575
2026-07-26T16:58:56Z
Wikihez
7344
/* Mediawiki:Gadget-citoid-helper.js */
6291576
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)
= 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î?
# [[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.js/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)
== [[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)
== 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)
== Gadget-linkify ==
* [[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)
== 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:
<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.
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)
= Moda tarî =
== MediaWiki:Newarticletext ==
* [[MediaWiki:Newarticletext]]: Divê rengên wê were rastkirin.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Modul:dara kategoriyan ==
* [[Modul:dara kategoriyan]] link_box —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== mane? ==
* {{ş|mane?}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== bêçavkanî ==
* {{ş|bêçavkanî}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== de-tew-nerm-ern ==
* {{ş|de-tew-nerm-ern}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 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)
== 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)
== [[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)
gkihvkbtj0bhz1cq4kxri276u3le0bv
6291599
6291576
2026-07-26T20:08:52Z
Wikihez
7344
/* MediaWiki:Newarticletext */ Bersiv
6291599
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)
= 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î?
# [[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.js/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)
== [[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)
== 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)
== Gadget-linkify ==
* [[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)
== 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:
<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.
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)
= Moda tarî =
== MediaWiki:Newarticletext ==
* [[MediaWiki:Newarticletext]]: Divê rengên wê were rastkirin.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
*:{{çêbû}} çavê min kor dikir wextê [[WF:Nû]] rast dikim {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:08, 26 tîrmeh 2026 (UTC)
== Modul:dara kategoriyan ==
* [[Modul:dara kategoriyan]] link_box —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== mane? ==
* {{ş|mane?}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== bêçavkanî ==
* {{ş|bêçavkanî}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== de-tew-nerm-ern ==
* {{ş|de-tew-nerm-ern}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 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)
== 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)
== [[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)
kmfk9luj8v40him5k6obxaov9ylg1ua
6291620
6291599
2026-07-26T23:24:16Z
Wikihez
7344
/* Modul:dara kategoriyan */ Bersiv
6291620
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)
= 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î?
# [[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.js/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)
== [[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)
== 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)
== Gadget-linkify ==
* [[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)
== 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:
<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.
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)
= Moda tarî =
== MediaWiki:Newarticletext ==
* [[MediaWiki:Newarticletext]]: Divê rengên wê were rastkirin.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
*:{{çêbû}} çavê min kor dikir wextê [[WF:Nû]] rast dikim {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:08, 26 tîrmeh 2026 (UTC)
== Modul:dara kategoriyan ==
* [[Modul:dara kategoriyan]] link_box —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
*:{{çêbû}} feqat [[Modul:dara kategoriyan/zimankatbike]] çênabe. Claude serê xwar dîsa nebû. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:24, 26 tîrmeh 2026 (UTC)
== mane? ==
* {{ş|mane?}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== bêçavkanî ==
* {{ş|bêçavkanî}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== de-tew-nerm-ern ==
* {{ş|de-tew-nerm-ern}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 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)
== 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)
== [[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)
8mzh8fyvl7ebn7vl5beviwmez73nwb2
6291622
6291620
2026-07-26T23:35:21Z
Wikihez
7344
/* Modul:dara kategoriyan */ Bersiv
6291622
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)
= 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î?
# [[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.js/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)
== [[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)
== 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)
== Gadget-linkify ==
* [[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)
== 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:
<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.
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)
= Moda tarî =
== MediaWiki:Newarticletext ==
* [[MediaWiki:Newarticletext]]: Divê rengên wê were rastkirin.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
*:{{çêbû}} çavê min kor dikir wextê [[WF:Nû]] rast dikim {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:08, 26 tîrmeh 2026 (UTC)
== Modul:dara kategoriyan ==
* [[Modul:dara kategoriyan]] link_box —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
*:{{çêbû}} feqat [[Modul:dara kategoriyan/zimankatbike]] çênabe. Claude serê xwar dîsa nebû. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:24, 26 tîrmeh 2026 (UTC)
*::ew jî çêbû [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:35, 26 tîrmeh 2026 (UTC)
== mane? ==
* {{ş|mane?}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== bêçavkanî ==
* {{ş|bêçavkanî}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== de-tew-nerm-ern ==
* {{ş|de-tew-nerm-ern}} —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 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)
== 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)
== [[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)
ca551i4o9ck3bz3kw0jzwb0uh6azqxm
Wîkîferheng:ceribandin/nimûne
4
1357865
6291562
6291543
2026-07-26T14:08:26Z
Wikihez
7344
6291562
wikitext
text/x-wiki
{{/vê rêzê neguhêre}}
== {{ziman|en}} ==
=== Lêker ===
[[Wêne:Example.jpg|thumb|Mînak]]
{{lêker|en}}
# Maneya 1ê.
#: {{mk|ku|'''Nimûneyeke''' bikaranînê ya vê peyvê.}}
#* {{jêder-kovar/ceribandin|en|nivîskar=Justin Chang|lînka-nivîskar=Justin Chang|sernav=The Day He Arrives [film review]|url=https://variety.com/2011/film/markets-festivals/the-day-he-arrives-1117945326/|urlya-arşîvê=https://web.archive.org/web/20170628015008/https://variety.com/2011/film/markets-festivals/the-day-he-arrives-1117945326/|tarîxa-arşîvê=28 June 2017|kovar=[[w:Variety (magazine)|Variety]]|tarîx=25 May 2011|issn=0042-2738|oclc=960911146|jêgirtin=The outline could apply to most of the self-effacing, increasingly inward-looking comedies written and directed by [[w:Hong Sang-soo|Hong Sang-soo]], and it certainly describes "[[w:The Day He Arrives|The Day He Arrives]]," an agreeably meandering exercise that brings some clever French New Wave '''fillips''' and structural repetitions to Hong's characteristically boozy party.}}
#* {{jêder-kitêb/ceribandin|en|nivîskar=F[rancis] Scott Fitzgerald|lînka-nivîskar=F. Scott Fitzgerald|beş=IX|sernav=[[w:The Great Gatsby|The Great Gatsby]]|cih=New York, N.Y.|weşanxane=[[w:Charles Scribner's Sons|Charles Scribner’s Sons]]|sal=1925|oclc=884653065|cih2=New York, N.Y.|weşanxane2=Charles Scribner’s Sons|sal2=1953|isbn2=978-0-684-16498-4|rûpel2=182|urlya-rûpelê2=https://archive.org/stream/in.ernet.dli.2015.184960/2015.184960.The-Great-Gatsby#page/n185/mode/1up|jêgirtin=So we beat on, boats against the current, borne back '''ceaselessly''' into the past.}}
#* {{jêder-malper/ceribandin|en|nivîskar=Meg Downey|sernav=Marvel’s Comic Soul Stone could Explain the Jump from Infinity War’s Ending to Avengers 4: A Brief History of Crazy Multidimensional Power|url=https://www.polygon.com/2018/5/7/17328230/infinity-war-avengers-4-marvel-comics-soul-stone|urlya-arşîvê=https://web.archive.org/web/20180624110542/https://www.polygon.com/2018/5/7/17328230/infinity-war-avengers-4-marvel-comics-soul-stone|tarîxa-arşîvê=24 June 2018|xebat=[[w:Polygon (website)|Polygon]]|tarîx=7 May 2018|jêgirtin=There’s a very real chance that, rather than crumbling into the dust and floating off into the '''ether''', Thanos’s victims [in the film ''[[w:Avengers: Infinity War|Avengers: Infinity War]]''] were actually sucked up into the Soul Stone.}}
#* {{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=The Republic of Molossia has played a considerable role in developing '''intermicronational''' diplomacy.}}
#* {{jêder-kitêb/ceribandin|en
|sal=c. 1988|nivîskar=Peter W. Rodino, Jr.; et al.|sernav=Report of the Congressional Committees Investigating the Iran/Contra Affair|urlya-rûpelê=http://books.google.com/books?id=ew_K3auTwEgC&pg=PA646&dq=phone+call|rûpel=646|jêgirtin=On November 20, they had a '''phone call''' and later a meeting with others lasting at least 1 1/2 hours.}}
#* {{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=Centrally acting '''sympathicolytics''' cause a decrease in the sympathetic outflow from the brainstem. This reduction in sympathetic outflow brings about vasodilatation of the peripheral vascular bed with a concomitant drop in the blood pressure}}
#* {{jêder-kitêb/ceribandin|en
|sal=2005|edîtor=Martin Endress; George Psathas; Hisashi Nasu|beş=Between the Everyday Life-World and the World of Social Scientific Theory—Towards an “Adequate” Social Thory|nivîskar=Hisashi Nasu|sernav=Explorations of the Life-World: Continuing Dialogues with Alfred Schutz|weşanxane=Springer|isbn=1402032196|rûpel=136|jêgirtin=It is not only the '''man on the street''' who is characterized by taking one's own system of relevance for granted. This holds true for the expert,....}}
#* {{jêder-kitêb/ceribandin|en
|sal=2002|nivîskar=Peter Shirlow|beş=Sinn Féin: Beyond; within Containment|hejmara-beşê=4|edîtor=Stefan Wolff; Jörg Neuheiser|sernav=Peace at Last?: The Impact of the Good Friday Agreement on Northern Ireland|weşanxane=Berghahn Books|isbn=157181518X|urlya-rûpelê=http://books.google.com/books?id=QyInASKCFjcC&pg=PA62&dq=ethnosectarian&sig=Q54T1Vz3vsKnqd05666N1ddFki8|rûpel=62|jêgirtin=Moreover, language, discourse, and dialogue, their definition and mobilisation, create chains of equivalence which enclose subjects within not only '''ethnosectarian''' but at times entirely self-referential and self-sustained notions of identity, practice and collective devotion.}}
#* {{jêder-kitêb/ceribandin|en
|sernav=Extreme Hydrological Events: New Concepts for Security|edîtor=O.F. Vasiliev; P.H.A.J.M. van Gelder; E.J. Plate; M.V. Bolgov|sal=2007|weşanxane=[[w:Springer Science+Business Media|Springer]]|isbn=1402057393|beş=Predetermination of Floods|nivîskar=Pierre Hubert; Ioulia Tchiguirinskaia; Daniel Schertzer; Hocine Bendjoudi; Shaun Lovejoy|qisim=On Probability and Return Periods of Floods|hejmara-qismê=2|rûpel=187|jêgirtin=It is perfectly legitimate to be interested, particularly for safety studies, by millennium floods or floods or '''decamillennium''' floods (i.e. having a probability 0,001 and 0,0001 respectively to occur in the course of given year) of a river which did not exist five thousand years ago and that will not exist perhaps any more in ten thousand years, similarly to the industrial interest in the probability of faults which will not undoubtedly occur, because the minimization of their probability is an aspect of a policy of industrial security.}}
#* {{jêder-kitêb/ceribandin|en
|sal=1995|nivîskar=w:Robert M. Solovay|nivîskarê-sereke=w:Kurt Gödel|edîtor=w:Solomon Feferman; w:John W. Dawson Jr.; w:Warren Goldfarb; {{w|Charles Parsons (philosopher)|Charles Parsons}}; w:Robert M. Solovay|beş=Notes to *1970 [{{w|Godel's ontological proof}}]|sernav=Kurt Gödel: Collected Works: Volume III|urlya-rûpelê=https://books.google.com.au/books?id=gDzbuUwma5MC&pg=PA419&dq=%22Hausdorff+gap%22%7C%22Hausdorff+gaps%22&hl=en&newbks=1&newbks_redir=0&sa=X&ved=2ahUKEwjo-o7D9OT7AhVQJUQIHaSlBIgQ6AF6BAhXEAI#v=onepage&q=%22Hausdorff%20gap%22%7C%22Hausdorff%20gaps%22&f=false|rûpel=419|weşanxane=w:Oxford University Press|jêgirtin=Of course, Hausdorff did not talk of models of set theory or prove absoluteness results. He gave a direct construction in ''ZFC'' of what is now called a '''Hausdorff gap''' and proved properties 1 through 4 for his construction. The notion of a '''Hausdorff gap''' and the proof that '''Hausdorff gaps''' are indestructible under cardinal preserving extensions (property 5 above) are due to Kunen (unpublished).}}
#* {{jêder-kitêb/ceribandin|en
|sal=1925|nivîskar={{w|F. Scott Fitzgerald|F[rancis] Scott Fitzgerald}}|beş=IX|sernav=w:The Great Gatsby|cih=New York, N.Y.|weşanxane=w:Charles Scribner's Sons|oclc=884653065|sal2=1953|cih2=New York, N.Y.|weşanxane2=Charles Scribner’s Sons|isbn2=978-0-684-16498-4|rûpel2=182|urlya-rûpelê2=https://archive.org/stream/in.ernet.dli.2015.184960/2015.184960.The-Great-Gatsby#page/n185/mode/1up|jêgirtin=So we beat on, boats against the current, borne back '''ceaselessly''' into the past.|lccn=gm 95-1}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar=w:John Boyle O'Reilly|beş=On the Trail|sernav=[[w:Moondyne|Moondyne: A Story from the Under-world]]|cih=London|weşanxane=[[w:Routledge|George Routledge and Sons]]|sal=1878|sala-weşandinê=1879|oclc=39983928|sernav2=Moondyne: A Story of Convict Life in Australia|cih2=London|weşanxane2=George Routledge & Sons, Limited, Broadway House, {{w|Ludgate Hill}}|sal2=[1880s]|qisim2=book first|pages2=23–24|urlya-rûpelê2=https://archive.org/stream/moondynestory00oreirich#page/24/mode/1up/|oclc2=83033698|jêgirtin=It was sore travelling for horse and man under the blazing sun, with no food nor water save what he pressed from the pith of the palms, and even these were growing scarce. The only life on the plains was the hard and dusty scrub. Every hour brought a more hopeless and '''grislier''' desolation.}}
#* {{jêder-kitêb/ceribandin|en
|sal=1966|sala-orîjînal=1858|nivîskar=w:Hans Christian Andersen|terciman=Pat Shaw Iversen|orîjînal=Suppe paa en Pølsepind|zimanê-orîjînal=da|beş=Soup from a Sausage Peg|sernav=The Snow Queen and Other Tales|serî=Signet Classic|cildê-seriyê=CT334|cih=New York, N.Y.|weşanxane=w:New American Library|rûpel=224|urlya-rûpelê=http://books.google.com/books?id=2tjWAAAAMAAJ&q=%22from+your+own+nook+and+cranny%22#search_anchor|oclc=636818779|metna-orîjînal=da:Det er underligt at komme hjemme fra sin egen '''Krinkelkrog''', gaae med Skib, der ogsaa er en Slags '''Krinkelkrog''', og saa pludselig være over hundrede Mile borte og staae i et fremmedt Land.|jêgirtin=It’s strange to come away from home, from your own '''nook and cranny''', to go by ship – which is also a kind of '''nook and cranny''' – and then suddenly be more than a hundred miles away and stand in a foreign land!}}
#* {{jêder-kitêb/ceribandin|en
|sal=2013|nivîskar=Bernd Klauer; Reiner Manstetten; Thomas Petersen; Johannes Schiller|zimanê-eserê=de|beş=Materielle Bestände|sernav=Die Kunst, langfristig zu denken : Wege zur Nachhaltigkeit<t:The art of thinking long-term : paths to sustainability>|cih=Baden-Baden|weşanxane=Nomos|versiyona-nû=translated as|terciman2=Kathleen Cross|sal2=2016|sernav2=Sustainability and the Art of Long-Term Thinking|beş2=Material stocks|cih2=London; New York|weşanxane2=Routledge, Taylor & Francis Group|qisim2=4.3.1. Material sets and stocks|rûpel2=44|isbn2=9781134986187|jêgirtin=The property of '''appertainment''' of a set is therefore the property which, by definition, has to be given in every element of the set.}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar={{w|Andrew Seth Pringle-Pattison|Andrew Seth}}|sernav=Hegelianism and Personality|çap=2nd|sal=1887|sala-weşandinê=1893|rûpel=216|rêz-sade=footnote|cih=Edinburgh; London|weşanxane=William Blackwood and Sons|versiyona-nû=quoted in|sal2=1893|2nivîskar=w:David George Ritchie|sernav2=Darwin and Hegel, with other Philosophical Studies|cih2=London|weşanxane2=S. Sonnenschein & Co.; New York, N.Y.: [[w:Macmillan Publishers (United States)|Macmillan & Co.]]|rûpel2=72|urlya-rûpelê2=http://books.google.com/books?id=8yGEAHvAwiIC&q=%22An+absolute+system+cannot+afford+to+leave+any+nook+or+cranny+of+existence+unexplored%22#search_anchor|rêz-sade2=note 1|oclc2=3299658|jêgirtin=An absolute system cannot afford to leave '''any nook or cranny''' of existence unexplored.}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar=w:David George Ritchie|jê-tê-girtin=w:Andrew Seth Pringle-Pattison|sernav=Hegelianism and Personality|çap=2nd|kesên-din=1893, page 216, footnote|cihê-jêgirtinê=Darwin and Hegel, with other Philosophical Studies|cih=London|weşanxane=S. Sonnenschein & Co.; New York, N.Y.: [[w:Macmillan Publishers (United States)|Macmillan & Co.]]|sal=1893|rûpel=72|rêz-sade=note 1|urlya-rûpelê=http://books.google.com/books?id=8yGEAHvAwiIC&q=%22An+absolute+system+cannot+afford+to+leave+any+nook+or+cranny+of+existence+unexplored%22#search_anchor|oclc=3299658|jêgirtin=An absolute system cannot afford to leave '''any nook or cranny''' of existence unexplored.}}
#* {{jêder-kovar/ceribandin|en
|nivîskar=w:Hayden Carruth|sernav=Making It New|kovar=w:The Hudson Review|cih=New York, N.Y.|weşanxane=Hudson Review, Inc.|meh=Summer|sal=1968|cild=XXI|hejmar=2|issn=2325-5935|oclc=920393805|versiyona-nû=reprinted as|cure2=book|beş2=From ‘Making It New’ [''Body Rags'']|edîtor2=Howard Nelson|sernav2=On the Poetry of Galway Kinnell: The Wages of Dying|serî2=Under Discussion|cih2=Ann Arbor, Mich.|weşanxane2={{w|University of Michigan Press}}|sal2=1987|rûpel2=75|urlya-rûpelê2=https://books.google.com/books?id=7cXV26PmJUYC&pg=PA75|isbn2=978-0-472-09376-2|jêgirtin=In his [{{w|Galway Kinnell}}'s] new book, ''Body Rags'', he has brought this style to a kind of perfection, especially in two poems about the killing of animals, "The Porcupine" and "The Bear." These are the '''grisliest''' poems I have ever read.}}
#* {{jêder-kitêb/ceribandin|fr
|sal=1973|nivîskar=w:Claude Simon|sernav=Tryptique|weşanxane=Éditions de Minuit|rûpel=12|jêgirtin=Les sons de la cloche '''égrenant''' les quarts, les demies et les heures {{...}}|t=The sounds of the clock '''rattling off''' the quarters, the halves and the hours {{...}}}}
#* {{jêder-kitêb/ceribandin|de
|sal=1999|nivîskar=Paul Hoffmann|hevnivîskar=Stefan H. Brandenburger; Ulrike Brauner; Thomas Hieke|sernav=Q 22:28, 30: You Will Judge the Twelve Tribes of Israel|serî=Documenta Q: Reconstructions of Q Through Two Centuries of Gospel Research Excerpted, Sorted and Evaluated|cildê-seriyê=volume 5|isbn=90-429-0734-7|rûpel=189|jêgirtin=Zumal in beiden Fällen eine Partizipialform vorliegt, liegt es nahe, die '''Zeitworte''' als Varianten anzusehen.}}
#* {{jêder-kitêb/ceribandin|ru
|nivîskar=lw:ru:[[Филарет (Дроздов)|Митрополи́т Филаре́т]]<tr:-><t:Mitropolit Filaret>|beş=ru:Сло́во предъ приведеніемъ къ присягѣ Санктпетербурксаго дворя́нства и гражданъ для избранія суде́й|tarîx=1818-02-01<!--Converted to Gregorian-->|sala-weşandinê=1873|cih=Moscow|sernav=ru:Сочинеиія Филарета Митрополита Московскаго и Коломенскаго|cild=I|rûpel=253|urlya-rûpelê=https://books.google.com/books?id=yWEcAAAAMAAJ&pg=PA253|jêgirtin=[[изъ|Изъ]] [[сей|сего́]] [[ви́дно]], [[что]] [[судья|судія́]], [[как|какъ]] [[блюсти́тель]] [[общественный|обще́ственнаго]] [[порядок|поря́дка]], [[должен|долженъ]] '''объима́ть''' [[и]] [[проника́ть]] [[мысль|мы́слію]] [[весь]] [[состав|соста́въ]] [[общество|о́бщества]];|t=From this it becomes discernible that a judge, in his function as a warden of public order, should '''enclose''' and traverse the whole body of the society;}}
#* {{jêder-kitêb/ceribandin|yi
|nivîskar=I.J. Singer|sernav=Fun a velt vos iz nishto mer|sal=1946|beş=yi:אַ ייד אַ מלמד פֿאַרגלוסט זיך צו ווערן אַ מלאך אום פּורים און פֿאַרלירט, נעבעך, אַן אויג<subst:ייד/ייִד,מלמד/מעלאַמעד,פּורים/פּורעם,מלאך/מאַלעך><t:A Jew, a teacher, decides to become an angel on Purim and, unfortunately, loses an eye>|jêgirtin=יעדן '''זמן''' אויפֿסניי, חול־המועד סוכות אָדער חול־המועד פּסח, בין איך געווען זייער האָפֿערדיק, וואָס מען האָט מיך גענומען איין מלמד און אָנגעדונגען צו אַן אַנדערן, גלײבנדיק, אַז דער נייער וועט זיין בעסער ווי דער אַלטער.|subst=זמן//זמאַן,חול־המועד//כאַלעמויעד,סוכות//סוקוס,פּסח//פּייסעך,מלמד//מעלאַמעד,נייער//נײַער|tercime=Every '''term''' anew, during the intermediary periods of Sukkot and Pesach, I was very confident in being taken from one melamed and given to another, believing the new one would be better than the old.}}
#* {{jêder-kitêb/ceribandin|ru
|sal=1983|nivîskar=w:Samuil Marshak|sernav=Баллада о королевском бутерброде<subst:бутер/бутэр><t:Ballad of the King's Bread>|orîjînal=The King's Breakfast|ji-aliyê=w:A. A. Milne|metna-orîjînal=en:"Nobody," / He whimpered, / "Could call me / A fussy man; / I only want / A little bit / Of butter for / My bread!"|zimanê-orîjînal=en|jêgirtin=— [[никто|Никто́]] [[не]] [[сказать|ска́жет]], [[бу́дто]] [[я]] / [[тиран|Тира́н]] [[и]] [[сумасбро́д]], / [[за|За]] [[то́]], [[что]] [[к]] [[чай|ча́ю]] [[я]] [[любить|люблю́]] / [[хороший|Хоро́ший]] '''бутербро́д'''.|subst=бутербро́д/бутэрбро́д|t="No one would say that I / Am a tyrant and madman, / For the fact that with my tea I like / A good '''bread-and-butter sandwich'''."}}
#* {{jêder-kitêb/ceribandin|ota
|sal=1922|sernav=ota:ادارهٔ حرب و سیاست<tr:Idare-i harb ve siyaset>|cih=Istanbul|weşanxane=ota:مطبعهٔ عثمانیه|rûpel=176|urlya-rûpelê=https://babel.hathitrust.org/cgi/pt?id=uc1.aa0006749931&view=1up&seq=182|versiyona-nû=translation of|ziman2=de|sal2=1922|2nivîskar=w:Erich Ludendorff|sernav2=Kriegsführung und Politik|cih2=Berlin|weşanxane2=E. S. Mittler & Sohn|rûpel2=199|urlya-rûpelê2=https://archive.org/details/kriegfhrungund00lude/page/199|jêgirtin=شرقی غالیچیا، و بوقوونیاده شمندوفر نقاباتی تأسس ایتدكدن صكره شرقده '''صلحی''' تأمین ایچون دینیهستر نهری جنوبندن روس — رومن جبههسنه قارشو حربه دوام ایتمك فكرندن فراغت ایتمك لازمكلیورایدی. بر قصقاچ تأثیری یاپهبیلمك ایچون بری قلاص غربندن یوقارهسهرهت اوزرینه و دیكری دینیهستر جنوبندن اولمق اوزره مضاعف بر تعرض اجراسی لازمكلیور ایده.|tr=Şarki Galiçya, ve Bukovinyada şömendüfer nakabatı tesis itdikden soñra şarkda sulhı temin içün Dinyester nehri cenubundan rus-rumen cebhesine karşu harbe devam itmek fikrinden feragat itmek lazım geliyor idi. Bir kıskaç tesiri yapabilmek içün biri Kalas garbından yukara-yı Seret üzerine ve diğeri Dinyester cenubundan olmak üzere muzaaf bir taarruz icrası lazım geliyor idi.|metna-orîjînal=de:Unsere weitere Absicht, nach Herstellung der Eisenbahnverbindungen in Ostgalizien und in der Bukowina zur Erzwingung des Friedens im Osten den Feldzug gegen die russisch-rumänische Front südlich des Dnjestr fortzusetzen, mußte aufgegeben werden. Er sollte in einem doppelten Angriff über den oberen Sereth westlich Galatz und hart südlich des Dnjestr bestehen und auch zangenartig wirken.|t=Our further intention, the military campaign against the Russian-Romanian front to be continued south of the Dniestr to force '''peace''' in the east, after procurement of the railroad connections in Eastern Galicia and in the Bukovina, had to be abandoned. It was supposed to consist in a double attack over the upper Siret west of Galați as well as hard south of the Dniestr and go like pincers.}}
#* {{jêder-kitêb/ceribandin|it
|sernav=L'œuvre complète de Tchouang-tseu|terciman=Liou Kia-hway<f:Hant:劉家槐><f:Pinyin:Liú Jiāhuái>|orîjînal={{w|Zhuangzi (book)|Zhuang-zi}}|ji-aliyê=莊周<t:Zhuang Zhou>|zimanê-orîjînal=och|sala-orîjînal=c. 300 {{BCE}}|sal=1969|sal2=1982|sala-weşandinê2=2010|versiyona-nû=translated as|terciman2=Carlo Laurenti; Christine Leverd|sernav2=Zhuang-zi [Chuang-tzu]|serî2=Gli Adelphi|cildê-seriyê2=41|beş2=Libertà naturale|tercimeya-beşê2=Natural Freedom|çap2=6th|cih2=Milan|weşanxane2={{w|Adelphi Edizioni}}|rûpel2=17|isbn2=978-88-459-0950-4|metna-orîjînal=och:之人也,物莫之傷,大浸稽天而不溺,大旱、金石流、土山焦而不熱。|jêgirtin=Se le acque si alzassero fino al cielo, non annegherebbero. Se la siccità '''liquefacesse''' i metalli e infiammasse le montagne, non ne sarebbero neppure sfiorati.|t=If the waters were to rise to the skies, they wouldn't drown. If drought were to '''liquefy''' metals, and set fire to the mountains, they wouldn't even be touched by it.}}
#* {{jêder-kitêb/ceribandin|fr
|zimanê-termê=en|sal=1677|nivîskar=Guy Miege [''i.e.'', [[w:fr:Guy Miège|Guy Miège]]]|made=HAGGESS|urlya-madeyê=https://books.google.com/books?id=YbWtTwVbLioC&pg=PP857|sernav=A New Dictionary French and English, with Another English and French; According to the Present Use, and Modern Orthography of the French. Inrich’d with New Words, Choice Phrases, and Apposite Proverbs; Digested into a Most Accurate Method; and Contrived for the Use Both of English and Foreiners|cih=London|weşanxane=Printed by Tho[mas] Dawks, for Thomas Basset, at the George, near [[w:Clifford's Inn|Cliffords-inn]], in [[w:Fleet Street|Fleetstreet]]|stûn=3|oclc=874955677|jêgirtin='''HAGGESS''', '''haggas''', or '''haggis''', ſort d'appret fait avec des herbes, du larde caupé menu, des épices, des œufs, & du fromage, le tout mis enſemble & bouilli dans une panſe de brebis.|t='''HAGGESS''', '''haggas''', or '''haggis''', a sort of preparation made with herbs, bacon, spices, eggs, cheese, all put together and boiled in a sheep's belly.|brackets=1}}
#*{{jêder-kovar/ceribandin|en|sernav=Art VIII.—Who are the True Conservatives?|kovar=The Quarterly Christian Spectator|cih=New Haven, Conn.|weşanxane=Published for the proprietor by Hezekiah Howe [...]|meh=November|sal=1838|cild=X|hejmar=IV|rûpel=608|urlya-rûpelê=https://books.google.com/books?id=S7MAAAAAMAAJ&pg=PA608|oclc=5090641|jêgirtin=They have guarded a few minds against '''sciolism''' and pretension in philosophy, and they have given a new and more imposing air to '''sciolism''' itself, and placed in the hands of the charlatan enchantments and magical arts, which hold fast its victims with a potency never granted to an inferior spell. There is no '''sciolism''' now which is more dangerous than that which is so very careful to inform us of its entire freedom from all that is superficial, {{...}}}}
#*{{jêder-kovar/ceribandin|en
|tarîx=March 8, 2023|paşnav=Wu|pêşnav=Huizhong|nivîskar2=Johnson Lai|sernav=Taiwan suspects Chinese ships cut islands’ internet cables|kovar={{w|The Washington Post}}|urlya-arşîvê=https://web.archive.org/web/20230311220100/https://www.washingtonpost.com/business/2023/03/08/matsu-taiwan-internet-cables-cut-china/d322fcae-bd70-11ed-9350-7c5fccd598ad_story.html|tarîxa-arşîvê=11 March 2023|issn=0190-8286|oclc=2269358|qisim=Business|tarîx2=April 18, 2023|sernav2=Chinese ships cut internet of Taiwan’s outlying islands|cure2=journal|kovar2={{w|AP News}}|urlya-arşîvê2=https://web.archive.org/web/20230308053048/https://apnews.com/article/matsu-taiwan-internet-cables-cut-china-65f10f5f73a346fa788436366d7a7c70|jêgirtin=Wang Chung Ming, the head of '''Lienchiang''' County, as the Matsu islands are officially called, said he and the legislator from Matsu went to Taipei shortly after the internet broke down to ask for help, and was told they would get priority in any future internet backup plans.{{...}}<br>Wang, the head of '''Lienchiang''' County, said he had mentioned the cables on a recent visit to China, where he had met an executive from China Mobile.}}
#*{{jêder-kovar/ceribandin|en
|sal=1994|meh=April|kovar=光華|tercimeya-kovar=Sinorama|url=https://books.google.com/books?id=GIRDAAAAYAAJ|cild=19|hejmar=1-6|weşanxane={{w|Government Information Office}}|issn=0256-9043|oclc=20549278|sal2=1994|meh2=April|2nivîskar=Yuan-ching Cheng|cure2=journal|sernav2=Kinmen- -From Battlefield to Tourist Destination|kovar2=台灣光華雜誌<t:Taiwan Panorama>|terciman2=Robert Taylor|urlya-arşîvê2=https://web.archive.org/web/20221021103917/https://www.taiwan-panorama.com/en/Articles/Details?Guid=c9335c12-7ec3-40e7-96be-33a81416a223|tarîxa-arşîvê2=21 October 2022|qisim2=Around Taiwan|jêgirtin=The temple to Colonel Li Kuang-chien lies near Hsiputou village in the northwest of Kinmen Island, around five kilometers from the town of '''Chincheng''' and next to the famous Hill 132.{{...}}<br>Because Kinmen was settled early, it bears many traces of the past. There are 21 listed historical monuments scattered all over the island. The only Grade I monument among them is the "Chiehhsiao Arch," commemorating Chiu Liang-kung's mother, located in East Houpu Street in the center of '''Chincheng'''.}}
== {{ziman|ku}} ==
=== Lêker ===
[[Wêne:Example.jpg|thumb|Mînak]]
{{lêker|ku}}
# [[testsdfsdafdasfas]]
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Mîr Kamûran di sala 1947 an de li '''Fransayê''' bicîh bû, û heta 1970ê li Xwendegeha zimanên rojhilatî ya Zaningeha Parîsê Profesorîya ziman û çanda Kurdî kir.|nivîskar=Kamuran A.Bedir Khan|nivîskar2=Josefe Bertolino|nivîskar3=Kendal Nezan|sernav=Institut Kurde De Paris Ferhenga Kurdî-Fransizî|cih=Istanbul|weşanxane={{w|Weşanên Avesta|Avesta}}|sal=2017|çap=1|rûpel=XXXIX|isbn=978-2-360134311}}
#* {{jêder-malper/ceribandin|ku|jêgirtin=Li gor amareke ajanseke selametiya xorakê, xelkê '''Fransa''' salane nêzîkî 4,000 ton Beqan dixwin, ku ew jî bi qasî goştê 40 nehengên şîn e. Ji bo dabînkirina pêwîstiya bazarê jî, li '''Fransayê''' kêlgeh û cihên taybet ên xwedîkirina Beqan hene. Beşek ji wêneyên di vê wênenûçeyê de, kêlgeha xwedîkirina Beqan li navçeyeke '''Fransayê''' ye, herwiha beşek ji wêneyan jî awayê patin û çêkirina xwarinê ji goştê Beqan di xwaringehan de û pêşkêşkirina wan bi mêvanan nîşan didin.|nivîskar=ciwan ikbal|sernav=Fransî di salekê de 4,000 ton Beqan dixwin|url=https://www.rudaw.net/kurmanci/world/101020201|dîrok=10.10.2020}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Bale awayeke dansê ye. Babetek ji şanoya musîkal e. Ji bo dansa baleyê, divê meriv cansivik û nerm be. Loma jî bale gelekî antremanê dixwaze û divê meriv di temenekî biçûk de dest pê bike. Di baleyê de, meriv li gor livên diyarkirî dansê dike. Hinek ji wan tu di wêne de dibînî. Ji ber ku bale li '''Fransayê''' bi pêş ketiye, loma navên livên wê jî bi [[fransî]] ne.|amadekar=Komxebat|sernav=Ansîklopediya Zarokan|cih=Denmark<br>|weşanxane={{w|Weşanên APECl|APEC}}|sal=2004|rûpel=25|isbn=91-89675-32-0}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Hekayêta Betal Kirina '''Xezeta''' Hetawî Kurd|sal=~1910|sala-weşandinê=2026|nivîskar=Mela Seîdê Şemdînanî|amadekar=David N. MacKenzie, Ergin Öpengin|url=http://books.google.com.tr/books?id=eqvrEQAAQBAJ&pg=PA383&dq=%22wan+xezetan%22+OR+%22xezet%C3%AA%22+OR+%22xezetin%22+OR+%22xezetek%C3%AA%22+OR+%22xezeta%22+OR+%22w%C3%AA+xezet%C3%AA%22+OR+%22xezetino%22+OR+%22xezetine%22&hl=&cd=2&source=gbs_api|sernav=Shamdinani Stories I: Kurdish Writings on the History and Culture of Central Kurdistan by Mullah Saʼid Shamdinani|weşanxane=BRILL|ISBN=9789004754430|rûpel=383}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Baş e, '''xezete''' dê bi çi ezmanî derkevit?|nivîskar=E. Rehmî Ji Mala Hekarîyan|amadekar=M. E. Bozarslan|sernav=Jîn: kovara Kurdî-Tirkî. Cild 1|weşanxane=Weşanxana Deng|cih=Uppsala|sala-weşandinê=1985|sal=1918|rûpel=310|isbn=978-91-970702-0-1}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=ligor{{sic}} danezanên '''xezete''' ên Turk hezar insan li kurdistan hatin kuştin ku ji % 70 jin û zaruk kal » pîrbûn|nivîskar=Hesen Hişyar Serdî|sernav=Dîtin û Bîrhatinên Min|weşanxane=Çapxana Emîral|cih=Beyrût|sal=1991|sala-weşandinê=2000|rûpel=380|url=https://bnk.institutkurde.org/images/pdf/CTNPMX4FR6.pdf}}
#* {{jêder-malper/ceribandin|ku|jêgirtin='''Xezeteyên''' Kurdî yên li Stenbolê derdiketin bên îstisnakirin, xezeteyekî millî û wetenî yê ku yekemîn car li Kurdistanê hatîye neşirkirin, ew jî xezeteya Zarê Kirmancî ye, ku min xwendîye.|nivîskar=Seîd Veroj|sernav=Mesûd Fanî (Dr. Mesûd Fanî Bîlgîlî)|websîte=Rûpela nû|url=https://www.rupelanu.com/seid-veroj-mesud-fani-dr-mesud-fani-bilgili-17258h.htm|tarîx=2021-05-04}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=me '''xezete''' da destê wî jî.|sal=2022|nivîskar=Abdurrahman Benek|url=http://books.google.com.tr/books?id=SdSUEAAAQBAJ&pg=PA33&dq=%22wan+xezetan%22+OR+%22xezet%C3%AA%22+OR+%22xezetin%22+OR+%22xezetek%C3%AA%22+OR+%22xezeta%22+OR+%22w%C3%AA+xezet%C3%AA%22+OR+%22xezetino%22+OR+%22xezetine%22&hl=&cd=3&source=gbs_api|sernav=Payiza Mezinan|weşanxane=Nûbihar|ISBN=9786059413749|rûpel=33}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Jêgirtinek ji kitêbekê.|nivîskar=Rojen Barnas|sernav=Sîlvan/ Silîvan / FARQÎN|dîrok=2013|weşandxane=Nûbihar|rûpel=189|isbn= 9786055402884, 6055402882}}
#* {{jêder-malper/ceribandin|ku|nivîskar=Gazete Duvar|sernav=Şairê dubeytan: Baba Tahirê Uryan|websîte=https://www.gazeteduvar.com.tr/saire-dubeytan-baba-tahire-uryan-haber-1753893|url=https://www.gazeteduvar.com.tr/saire-dubeytan-baba-tahire-uryan-haber-1753893|sal=2025|tarîx=2025-02-02|jêgirtin=adfasdf}}
#* {{jêder-kitêb/ceribandin|ku|sernav=Shamdinani Stories I|weşanxane=Brill|sal=2026|isbn=978-90-04-75443-0|url=https://brill.com/edcollbook-oa/title/74006|jêgirtin=test}}
# Maneya 3an.
ej0zrgw3znt160qiw8b7tmsfdxmefje
6291563
6291562
2026-07-26T14:09:14Z
Wikihez
7344
6291563
wikitext
text/x-wiki
{{/vê rêzê neguhêre}}
== {{ziman|en}} ==
=== Lêker ===
[[Wêne:Example.jpg|thumb|Mînak]]
{{lêker|en}}
# Maneya 1ê.
#: {{mk|ku|'''Nimûneyeke''' bikaranînê ya vê peyvê.}}
#* {{jêder-kovar/ceribandin|en|nivîskar=Justin Chang|lînka-nivîskar=Justin Chang|sernav=The Day He Arrives [film review]|url=https://variety.com/2011/film/markets-festivals/the-day-he-arrives-1117945326/|urlya-arşîvê=https://web.archive.org/web/20170628015008/https://variety.com/2011/film/markets-festivals/the-day-he-arrives-1117945326/|tarîxa-arşîvê=28 June 2017|kovar=[[w:Variety (magazine)|Variety]]|tarîx=25 May 2011|issn=0042-2738|oclc=960911146|jêgirtin=The outline could apply to most of the self-effacing, increasingly inward-looking comedies written and directed by [[w:Hong Sang-soo|Hong Sang-soo]], and it certainly describes "[[w:The Day He Arrives|The Day He Arrives]]," an agreeably meandering exercise that brings some clever French New Wave '''fillips''' and structural repetitions to Hong's characteristically boozy party.}}
#* {{jêder-kitêb/ceribandin|en|nivîskar=F[rancis] Scott Fitzgerald|lînka-nivîskar=F. Scott Fitzgerald|beş=IX|sernav=[[w:The Great Gatsby|The Great Gatsby]]|cih=New York, N.Y.|weşanxane=[[w:Charles Scribner's Sons|Charles Scribner’s Sons]]|sal=1925|oclc=884653065|cih2=New York, N.Y.|weşanxane2=Charles Scribner’s Sons|sal2=1953|isbn2=978-0-684-16498-4|rûpel2=182|urlya-rûpelê2=https://archive.org/stream/in.ernet.dli.2015.184960/2015.184960.The-Great-Gatsby#page/n185/mode/1up|jêgirtin=So we beat on, boats against the current, borne back '''ceaselessly''' into the past.}}
#* {{jêder-malper/ceribandin|en|nivîskar=Meg Downey|sernav=Marvel’s Comic Soul Stone could Explain the Jump from Infinity War’s Ending to Avengers 4: A Brief History of Crazy Multidimensional Power|url=https://www.polygon.com/2018/5/7/17328230/infinity-war-avengers-4-marvel-comics-soul-stone|urlya-arşîvê=https://web.archive.org/web/20180624110542/https://www.polygon.com/2018/5/7/17328230/infinity-war-avengers-4-marvel-comics-soul-stone|tarîxa-arşîvê=24 June 2018|xebat=[[w:Polygon (website)|Polygon]]|tarîx=7 May 2018|jêgirtin=There’s a very real chance that, rather than crumbling into the dust and floating off into the '''ether''', Thanos’s victims [in the film ''[[w:Avengers: Infinity War|Avengers: Infinity War]]''] were actually sucked up into the Soul Stone.}}
#* {{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=The Republic of Molossia has played a considerable role in developing '''intermicronational''' diplomacy.}}
#* {{jêder-kitêb/ceribandin|en
|sal=c. 1988|nivîskar=Peter W. Rodino, Jr.; et al.|sernav=Report of the Congressional Committees Investigating the Iran/Contra Affair|urlya-rûpelê=http://books.google.com/books?id=ew_K3auTwEgC&pg=PA646&dq=phone+call|rûpel=646|jêgirtin=On November 20, they had a '''phone call''' and later a meeting with others lasting at least 1 1/2 hours.}}
#* {{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=Centrally acting '''sympathicolytics''' cause a decrease in the sympathetic outflow from the brainstem. This reduction in sympathetic outflow brings about vasodilatation of the peripheral vascular bed with a concomitant drop in the blood pressure}}
#* {{jêder-kitêb/ceribandin|en
|sal=2005|edîtor=Martin Endress; George Psathas; Hisashi Nasu|beş=Between the Everyday Life-World and the World of Social Scientific Theory—Towards an “Adequate” Social Thory|nivîskar=Hisashi Nasu|sernav=Explorations of the Life-World: Continuing Dialogues with Alfred Schutz|weşanxane=Springer|isbn=1402032196|rûpel=136|jêgirtin=It is not only the '''man on the street''' who is characterized by taking one's own system of relevance for granted. This holds true for the expert,....}}
#* {{jêder-kitêb/ceribandin|en
|sal=2002|nivîskar=Peter Shirlow|beş=Sinn Féin: Beyond; within Containment|hejmara-beşê=4|edîtor=Stefan Wolff; Jörg Neuheiser|sernav=Peace at Last?: The Impact of the Good Friday Agreement on Northern Ireland|weşanxane=Berghahn Books|isbn=157181518X|urlya-rûpelê=http://books.google.com/books?id=QyInASKCFjcC&pg=PA62&dq=ethnosectarian&sig=Q54T1Vz3vsKnqd05666N1ddFki8|rûpel=62|jêgirtin=Moreover, language, discourse, and dialogue, their definition and mobilisation, create chains of equivalence which enclose subjects within not only '''ethnosectarian''' but at times entirely self-referential and self-sustained notions of identity, practice and collective devotion.}}
#* {{jêder-kitêb/ceribandin|en
|sernav=Extreme Hydrological Events: New Concepts for Security|edîtor=O.F. Vasiliev; P.H.A.J.M. van Gelder; E.J. Plate; M.V. Bolgov|sal=2007|weşanxane=[[w:Springer Science+Business Media|Springer]]|isbn=1402057393|beş=Predetermination of Floods|nivîskar=Pierre Hubert; Ioulia Tchiguirinskaia; Daniel Schertzer; Hocine Bendjoudi; Shaun Lovejoy|qisim=On Probability and Return Periods of Floods|hejmara-qismê=2|rûpel=187|jêgirtin=It is perfectly legitimate to be interested, particularly for safety studies, by millennium floods or floods or '''decamillennium''' floods (i.e. having a probability 0,001 and 0,0001 respectively to occur in the course of given year) of a river which did not exist five thousand years ago and that will not exist perhaps any more in ten thousand years, similarly to the industrial interest in the probability of faults which will not undoubtedly occur, because the minimization of their probability is an aspect of a policy of industrial security.}}
#* {{jêder-kitêb/ceribandin|en
|sal=1995|nivîskar=w:Robert M. Solovay|nivîskarê-sereke=w:Kurt Gödel|edîtor=w:Solomon Feferman; w:John W. Dawson Jr.; w:Warren Goldfarb; {{w|Charles Parsons (philosopher)|Charles Parsons}}; w:Robert M. Solovay|beş=Notes to *1970 [{{w|Godel's ontological proof}}]|sernav=Kurt Gödel: Collected Works: Volume III|urlya-rûpelê=https://books.google.com.au/books?id=gDzbuUwma5MC&pg=PA419&dq=%22Hausdorff+gap%22%7C%22Hausdorff+gaps%22&hl=en&newbks=1&newbks_redir=0&sa=X&ved=2ahUKEwjo-o7D9OT7AhVQJUQIHaSlBIgQ6AF6BAhXEAI#v=onepage&q=%22Hausdorff%20gap%22%7C%22Hausdorff%20gaps%22&f=false|rûpel=419|weşanxane=w:Oxford University Press|jêgirtin=Of course, Hausdorff did not talk of models of set theory or prove absoluteness results. He gave a direct construction in ''ZFC'' of what is now called a '''Hausdorff gap''' and proved properties 1 through 4 for his construction. The notion of a '''Hausdorff gap''' and the proof that '''Hausdorff gaps''' are indestructible under cardinal preserving extensions (property 5 above) are due to Kunen (unpublished).}}
#* {{jêder-kitêb/ceribandin|en
|sal=1925|nivîskar={{w|F. Scott Fitzgerald|F[rancis] Scott Fitzgerald}}|beş=IX|sernav=w:The Great Gatsby|cih=New York, N.Y.|weşanxane=w:Charles Scribner's Sons|oclc=884653065|sal2=1953|cih2=New York, N.Y.|weşanxane2=Charles Scribner’s Sons|isbn2=978-0-684-16498-4|rûpel2=182|urlya-rûpelê2=https://archive.org/stream/in.ernet.dli.2015.184960/2015.184960.The-Great-Gatsby#page/n185/mode/1up|jêgirtin=So we beat on, boats against the current, borne back '''ceaselessly''' into the past.|lccn=gm 95-1}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar=w:John Boyle O'Reilly|beş=On the Trail|sernav=[[w:Moondyne|Moondyne: A Story from the Under-world]]|cih=London|weşanxane=[[w:Routledge|George Routledge and Sons]]|sal=1878|sala-weşandinê=1879|oclc=39983928|sernav2=Moondyne: A Story of Convict Life in Australia|cih2=London|weşanxane2=George Routledge & Sons, Limited, Broadway House, {{w|Ludgate Hill}}|sal2=[1880s]|qisim2=book first|pages2=23–24|urlya-rûpelê2=https://archive.org/stream/moondynestory00oreirich#page/24/mode/1up/|oclc2=83033698|jêgirtin=It was sore travelling for horse and man under the blazing sun, with no food nor water save what he pressed from the pith of the palms, and even these were growing scarce. The only life on the plains was the hard and dusty scrub. Every hour brought a more hopeless and '''grislier''' desolation.}}
#* {{jêder-kitêb/ceribandin|en
|sal=1966|sala-orîjînal=1858|nivîskar=w:Hans Christian Andersen|terciman=Pat Shaw Iversen|orîjînal=Suppe paa en Pølsepind|zimanê-orîjînal=da|beş=Soup from a Sausage Peg|sernav=The Snow Queen and Other Tales|serî=Signet Classic|cildê-seriyê=CT334|cih=New York, N.Y.|weşanxane=w:New American Library|rûpel=224|urlya-rûpelê=http://books.google.com/books?id=2tjWAAAAMAAJ&q=%22from+your+own+nook+and+cranny%22#search_anchor|oclc=636818779|metna-orîjînal=da:Det er underligt at komme hjemme fra sin egen '''Krinkelkrog''', gaae med Skib, der ogsaa er en Slags '''Krinkelkrog''', og saa pludselig være over hundrede Mile borte og staae i et fremmedt Land.|jêgirtin=It’s strange to come away from home, from your own '''nook and cranny''', to go by ship – which is also a kind of '''nook and cranny''' – and then suddenly be more than a hundred miles away and stand in a foreign land!}}
#* {{jêder-kitêb/ceribandin|en
|sal=2013|nivîskar=Bernd Klauer; Reiner Manstetten; Thomas Petersen; Johannes Schiller|zimanê-eserê=de|beş=Materielle Bestände|sernav=Die Kunst, langfristig zu denken : Wege zur Nachhaltigkeit<t:The art of thinking long-term : paths to sustainability>|cih=Baden-Baden|weşanxane=Nomos|versiyona-nû=translated as|terciman2=Kathleen Cross|sal2=2016|sernav2=Sustainability and the Art of Long-Term Thinking|beş2=Material stocks|cih2=London; New York|weşanxane2=Routledge, Taylor & Francis Group|qisim2=4.3.1. Material sets and stocks|rûpel2=44|isbn2=9781134986187|jêgirtin=The property of '''appertainment''' of a set is therefore the property which, by definition, has to be given in every element of the set.}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar={{w|Andrew Seth Pringle-Pattison|Andrew Seth}}|sernav=Hegelianism and Personality|çap=2nd|sal=1887|sala-weşandinê=1893|rûpel=216|rêz-sade=footnote|cih=Edinburgh; London|weşanxane=William Blackwood and Sons|versiyona-nû=quoted in|sal2=1893|2nivîskar=w:David George Ritchie|sernav2=Darwin and Hegel, with other Philosophical Studies|cih2=London|weşanxane2=S. Sonnenschein & Co.; New York, N.Y.: [[w:Macmillan Publishers (United States)|Macmillan & Co.]]|rûpel2=72|urlya-rûpelê2=http://books.google.com/books?id=8yGEAHvAwiIC&q=%22An+absolute+system+cannot+afford+to+leave+any+nook+or+cranny+of+existence+unexplored%22#search_anchor|rêz-sade2=note 1|oclc2=3299658|jêgirtin=An absolute system cannot afford to leave '''any nook or cranny''' of existence unexplored.}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar=w:David George Ritchie|jê-tê-girtin=w:Andrew Seth Pringle-Pattison|sernav=Hegelianism and Personality|çap=2nd|kesên-din=1893, page 216, footnote|cihê-jêgirtinê=Darwin and Hegel, with other Philosophical Studies|cih=London|weşanxane=S. Sonnenschein & Co.; New York, N.Y.: [[w:Macmillan Publishers (United States)|Macmillan & Co.]]|sal=1893|rûpel=72|rêz-sade=note 1|urlya-rûpelê=http://books.google.com/books?id=8yGEAHvAwiIC&q=%22An+absolute+system+cannot+afford+to+leave+any+nook+or+cranny+of+existence+unexplored%22#search_anchor|oclc=3299658|jêgirtin=An absolute system cannot afford to leave '''any nook or cranny''' of existence unexplored.}}
#* {{jêder-kovar/ceribandin|en
|nivîskar=w:Hayden Carruth|sernav=Making It New|kovar=w:The Hudson Review|cih=New York, N.Y.|weşanxane=Hudson Review, Inc.|meh=Summer|sal=1968|cild=XXI|hejmar=2|issn=2325-5935|oclc=920393805|versiyona-nû=reprinted as|cure2=book|beş2=From ‘Making It New’ [''Body Rags'']|edîtor2=Howard Nelson|sernav2=On the Poetry of Galway Kinnell: The Wages of Dying|serî2=Under Discussion|cih2=Ann Arbor, Mich.|weşanxane2={{w|University of Michigan Press}}|sal2=1987|rûpel2=75|urlya-rûpelê2=https://books.google.com/books?id=7cXV26PmJUYC&pg=PA75|isbn2=978-0-472-09376-2|jêgirtin=In his [{{w|Galway Kinnell}}'s] new book, ''Body Rags'', he has brought this style to a kind of perfection, especially in two poems about the killing of animals, "The Porcupine" and "The Bear." These are the '''grisliest''' poems I have ever read.}}
#* {{jêder-kitêb/ceribandin|fr
|sal=1973|nivîskar=w:Claude Simon|sernav=Tryptique|weşanxane=Éditions de Minuit|rûpel=12|jêgirtin=Les sons de la cloche '''égrenant''' les quarts, les demies et les heures {{...}}|t=The sounds of the clock '''rattling off''' the quarters, the halves and the hours {{...}}}}
#* {{jêder-kitêb/ceribandin|de
|sal=1999|nivîskar=Paul Hoffmann|hevnivîskar=Stefan H. Brandenburger; Ulrike Brauner; Thomas Hieke|sernav=Q 22:28, 30: You Will Judge the Twelve Tribes of Israel|serî=Documenta Q: Reconstructions of Q Through Two Centuries of Gospel Research Excerpted, Sorted and Evaluated|cildê-seriyê=volume 5|isbn=90-429-0734-7|rûpel=189|jêgirtin=Zumal in beiden Fällen eine Partizipialform vorliegt, liegt es nahe, die '''Zeitworte''' als Varianten anzusehen.}}
#* {{jêder-kitêb/ceribandin|ru
|nivîskar=lw:ru:[[Филарет (Дроздов)|Митрополи́т Филаре́т]]<tr:-><t:Mitropolit Filaret>|beş=ru:Сло́во предъ приведеніемъ къ присягѣ Санктпетербурксаго дворя́нства и гражданъ для избранія суде́й|tarîx=1818-02-01<!--Converted to Gregorian-->|sala-weşandinê=1873|cih=Moscow|sernav=ru:Сочинеиія Филарета Митрополита Московскаго и Коломенскаго|cild=I|rûpel=253|urlya-rûpelê=https://books.google.com/books?id=yWEcAAAAMAAJ&pg=PA253|jêgirtin=[[изъ|Изъ]] [[сей|сего́]] [[ви́дно]], [[что]] [[судья|судія́]], [[как|какъ]] [[блюсти́тель]] [[общественный|обще́ственнаго]] [[порядок|поря́дка]], [[должен|долженъ]] '''объима́ть''' [[и]] [[проника́ть]] [[мысль|мы́слію]] [[весь]] [[состав|соста́въ]] [[общество|о́бщества]];|t=From this it becomes discernible that a judge, in his function as a warden of public order, should '''enclose''' and traverse the whole body of the society;}}
#* {{jêder-kitêb/ceribandin|yi
|nivîskar=I.J. Singer|sernav=Fun a velt vos iz nishto mer|sal=1946|beş=yi:אַ ייד אַ מלמד פֿאַרגלוסט זיך צו ווערן אַ מלאך אום פּורים און פֿאַרלירט, נעבעך, אַן אויג<subst:ייד/ייִד,מלמד/מעלאַמעד,פּורים/פּורעם,מלאך/מאַלעך><t:A Jew, a teacher, decides to become an angel on Purim and, unfortunately, loses an eye>|jêgirtin=יעדן '''זמן''' אויפֿסניי, חול־המועד סוכות אָדער חול־המועד פּסח, בין איך געווען זייער האָפֿערדיק, וואָס מען האָט מיך גענומען איין מלמד און אָנגעדונגען צו אַן אַנדערן, גלײבנדיק, אַז דער נייער וועט זיין בעסער ווי דער אַלטער.|subst=זמן//זמאַן,חול־המועד//כאַלעמויעד,סוכות//סוקוס,פּסח//פּייסעך,מלמד//מעלאַמעד,נייער//נײַער|tercime=Every '''term''' anew, during the intermediary periods of Sukkot and Pesach, I was very confident in being taken from one melamed and given to another, believing the new one would be better than the old.}}
#* {{jêder-kitêb/ceribandin|ru
|sal=1983|nivîskar=w:Samuil Marshak|sernav=Баллада о королевском бутерброде<subst:бутер/бутэр><t:Ballad of the King's Bread>|orîjînal=The King's Breakfast|ji-aliyê=w:A. A. Milne|metna-orîjînal=en:"Nobody," / He whimpered, / "Could call me / A fussy man; / I only want / A little bit / Of butter for / My bread!"|zimanê-orîjînal=en|jêgirtin=— [[никто|Никто́]] [[не]] [[сказать|ска́жет]], [[бу́дто]] [[я]] / [[тиран|Тира́н]] [[и]] [[сумасбро́д]], / [[за|За]] [[то́]], [[что]] [[к]] [[чай|ча́ю]] [[я]] [[любить|люблю́]] / [[хороший|Хоро́ший]] '''бутербро́д'''.|subst=бутербро́д/бутэрбро́д|t="No one would say that I / Am a tyrant and madman, / For the fact that with my tea I like / A good '''bread-and-butter sandwich'''."}}
#* {{jêder-kitêb/ceribandin|ota
|sal=1922|sernav=ota:ادارهٔ حرب و سیاست<tr:Idare-i harb ve siyaset>|cih=Istanbul|weşanxane=ota:مطبعهٔ عثمانیه|rûpel=176|urlya-rûpelê=https://babel.hathitrust.org/cgi/pt?id=uc1.aa0006749931&view=1up&seq=182|versiyona-nû=translation of|ziman2=de|sal2=1922|2nivîskar=w:Erich Ludendorff|sernav2=Kriegsführung und Politik|cih2=Berlin|weşanxane2=E. S. Mittler & Sohn|rûpel2=199|urlya-rûpelê2=https://archive.org/details/kriegfhrungund00lude/page/199|jêgirtin=شرقی غالیچیا، و بوقوونیاده شمندوفر نقاباتی تأسس ایتدكدن صكره شرقده '''صلحی''' تأمین ایچون دینیهستر نهری جنوبندن روس — رومن جبههسنه قارشو حربه دوام ایتمك فكرندن فراغت ایتمك لازمكلیورایدی. بر قصقاچ تأثیری یاپهبیلمك ایچون بری قلاص غربندن یوقارهسهرهت اوزرینه و دیكری دینیهستر جنوبندن اولمق اوزره مضاعف بر تعرض اجراسی لازمكلیور ایده.|tr=Şarki Galiçya, ve Bukovinyada şömendüfer nakabatı tesis itdikden soñra şarkda sulhı temin içün Dinyester nehri cenubundan rus-rumen cebhesine karşu harbe devam itmek fikrinden feragat itmek lazım geliyor idi. Bir kıskaç tesiri yapabilmek içün biri Kalas garbından yukara-yı Seret üzerine ve diğeri Dinyester cenubundan olmak üzere muzaaf bir taarruz icrası lazım geliyor idi.|metna-orîjînal=de:Unsere weitere Absicht, nach Herstellung der Eisenbahnverbindungen in Ostgalizien und in der Bukowina zur Erzwingung des Friedens im Osten den Feldzug gegen die russisch-rumänische Front südlich des Dnjestr fortzusetzen, mußte aufgegeben werden. Er sollte in einem doppelten Angriff über den oberen Sereth westlich Galatz und hart südlich des Dnjestr bestehen und auch zangenartig wirken.|t=Our further intention, the military campaign against the Russian-Romanian front to be continued south of the Dniestr to force '''peace''' in the east, after procurement of the railroad connections in Eastern Galicia and in the Bukovina, had to be abandoned. It was supposed to consist in a double attack over the upper Siret west of Galați as well as hard south of the Dniestr and go like pincers.}}
#* {{jêder-kitêb/ceribandin|it
|sernav=L'œuvre complète de Tchouang-tseu|terciman=Liou Kia-hway<f:Hant:劉家槐><f:Pinyin:Liú Jiāhuái>|orîjînal={{w|Zhuangzi (book)|Zhuang-zi}}|ji-aliyê=莊周<t:Zhuang Zhou>|zimanê-orîjînal=och|sala-orîjînal=c. 300 {{BCE}}|sal=1969|sal2=1982|sala-weşandinê2=2010|versiyona-nû=translated as|terciman2=Carlo Laurenti; Christine Leverd|sernav2=Zhuang-zi [Chuang-tzu]|serî2=Gli Adelphi|cildê-seriyê2=41|beş2=Libertà naturale|tercimeya-beşê2=Natural Freedom|çap2=6th|cih2=Milan|weşanxane2={{w|Adelphi Edizioni}}|rûpel2=17|isbn2=978-88-459-0950-4|metna-orîjînal=och:之人也,物莫之傷,大浸稽天而不溺,大旱、金石流、土山焦而不熱。|jêgirtin=Se le acque si alzassero fino al cielo, non annegherebbero. Se la siccità '''liquefacesse''' i metalli e infiammasse le montagne, non ne sarebbero neppure sfiorati.|t=If the waters were to rise to the skies, they wouldn't drown. If drought were to '''liquefy''' metals, and set fire to the mountains, they wouldn't even be touched by it.}}
#* {{jêder-kitêb/ceribandin|fr
|zimanê-termê=en|sal=1677|nivîskar=Guy Miege [''i.e.'', [[w:fr:Guy Miège|Guy Miège]]]|made=HAGGESS|urlya-madeyê=https://books.google.com/books?id=YbWtTwVbLioC&pg=PP857|sernav=A New Dictionary French and English, with Another English and French; According to the Present Use, and Modern Orthography of the French. Inrich’d with New Words, Choice Phrases, and Apposite Proverbs; Digested into a Most Accurate Method; and Contrived for the Use Both of English and Foreiners|cih=London|weşanxane=Printed by Tho[mas] Dawks, for Thomas Basset, at the George, near [[w:Clifford's Inn|Cliffords-inn]], in [[w:Fleet Street|Fleetstreet]]|stûn=3|oclc=874955677|jêgirtin='''HAGGESS''', '''haggas''', or '''haggis''', ſort d'appret fait avec des herbes, du larde caupé menu, des épices, des œufs, & du fromage, le tout mis enſemble & bouilli dans une panſe de brebis.|t='''HAGGESS''', '''haggas''', or '''haggis''', a sort of preparation made with herbs, bacon, spices, eggs, cheese, all put together and boiled in a sheep's belly.|brackets=1}}
#*{{jêder-kovar/ceribandin|en|sernav=Art VIII.—Who are the True Conservatives?|kovar=The Quarterly Christian Spectator|cih=New Haven, Conn.|weşanxane=Published for the proprietor by Hezekiah Howe [...]|meh=November|sal=1838|cild=X|hejmar=IV|rûpel=608|urlya-rûpelê=https://books.google.com/books?id=S7MAAAAAMAAJ&pg=PA608|oclc=5090641|jêgirtin=They have guarded a few minds against '''sciolism''' and pretension in philosophy, and they have given a new and more imposing air to '''sciolism''' itself, and placed in the hands of the charlatan enchantments and magical arts, which hold fast its victims with a potency never granted to an inferior spell. There is no '''sciolism''' now which is more dangerous than that which is so very careful to inform us of its entire freedom from all that is superficial, {{...}}}}
#*{{jêder-kovar/ceribandin|en
|tarîx=March 8, 2023|paşnav=Wu|pêşnav=Huizhong|nivîskar2=Johnson Lai|sernav=Taiwan suspects Chinese ships cut islands’ internet cables|kovar={{w|The Washington Post}}|urlya-arşîvê=https://web.archive.org/web/20230311220100/https://www.washingtonpost.com/business/2023/03/08/matsu-taiwan-internet-cables-cut-china/d322fcae-bd70-11ed-9350-7c5fccd598ad_story.html|tarîxa-arşîvê=11 March 2023|issn=0190-8286|oclc=2269358|qisim=Business|tarîx2=April 18, 2023|sernav2=Chinese ships cut internet of Taiwan’s outlying islands|cure2=journal|kovar2={{w|AP News}}|urlya-arşîvê2=https://web.archive.org/web/20230308053048/https://apnews.com/article/matsu-taiwan-internet-cables-cut-china-65f10f5f73a346fa788436366d7a7c70|jêgirtin=Wang Chung Ming, the head of '''Lienchiang''' County, as the Matsu islands are officially called, said he and the legislator from Matsu went to Taipei shortly after the internet broke down to ask for help, and was told they would get priority in any future internet backup plans.{{...}}<br>Wang, the head of '''Lienchiang''' County, said he had mentioned the cables on a recent visit to China, where he had met an executive from China Mobile.}}
#*{{jêder-kovar/ceribandin|en
|sal=1994|meh=April|kovar=光華|tercimeya-kovar=Sinorama|url=https://books.google.com/books?id=GIRDAAAAYAAJ|cild=19|hejmar=1-6|weşanxane={{w|Government Information Office}}|issn=0256-9043|oclc=20549278|sal2=1994|meh2=April|2nivîskar=Yuan-ching Cheng|cure2=journal|sernav2=Kinmen- -From Battlefield to Tourist Destination|kovar2=台灣光華雜誌<t:Taiwan Panorama>|terciman2=Robert Taylor|urlya-arşîvê2=https://web.archive.org/web/20221021103917/https://www.taiwan-panorama.com/en/Articles/Details?Guid=c9335c12-7ec3-40e7-96be-33a81416a223|tarîxa-arşîvê2=21 October 2022|qisim2=Around Taiwan|jêgirtin=The temple to Colonel Li Kuang-chien lies near Hsiputou village in the northwest of Kinmen Island, around five kilometers from the town of '''Chincheng''' and next to the famous Hill 132.{{...}}<br>Because Kinmen was settled early, it bears many traces of the past. There are 21 listed historical monuments scattered all over the island. The only Grade I monument among them is the "Chiehhsiao Arch," commemorating Chiu Liang-kung's mother, located in East Houpu Street in the center of '''Chincheng'''.}}
== {{ziman|ku}} ==
=== Lêker ===
[[Wêne:Example.jpg|thumb|Mînak]]
{{lêker|ku}}
# [[testsdfsdafdasfas]]
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Mîr Kamûran di sala 1947 an de li '''Fransayê''' bicîh bû, û heta 1970ê li Xwendegeha zimanên rojhilatî ya Zaningeha Parîsê Profesorîya ziman û çanda Kurdî kir.|nivîskar=Kamuran A.Bedir Khan|nivîskar2=Josefe Bertolino|nivîskar3=Kendal Nezan|sernav=Institut Kurde De Paris Ferhenga Kurdî-Fransizî|cih=Istanbul|weşanxane={{w|Weşanên Avesta|Avesta}}|sal=2017|çap=1|rûpel=XXXIX|isbn=978-2-360134311}}
#* {{jêder-malper/ceribandin|ku|jêgirtin=Li gor amareke ajanseke selametiya xorakê, xelkê '''Fransa''' salane nêzîkî 4,000 ton Beqan dixwin, ku ew jî bi qasî goştê 40 nehengên şîn e. Ji bo dabînkirina pêwîstiya bazarê jî, li '''Fransayê''' kêlgeh û cihên taybet ên xwedîkirina Beqan hene. Beşek ji wêneyên di vê wênenûçeyê de, kêlgeha xwedîkirina Beqan li navçeyeke '''Fransayê''' ye, herwiha beşek ji wêneyan jî awayê patin û çêkirina xwarinê ji goştê Beqan di xwaringehan de û pêşkêşkirina wan bi mêvanan nîşan didin.|nivîskar=ciwan ikbal|sernav=Fransî di salekê de 4,000 ton Beqan dixwin|url=https://www.rudaw.net/kurmanci/world/101020201|dîrok=10.10.2020}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Bale awayeke dansê ye. Babetek ji şanoya musîkal e. Ji bo dansa baleyê, divê meriv cansivik û nerm be. Loma jî bale gelekî antremanê dixwaze û divê meriv di temenekî biçûk de dest pê bike. Di baleyê de, meriv li gor livên diyarkirî dansê dike. Hinek ji wan tu di wêne de dibînî. Ji ber ku bale li '''Fransayê''' bi pêş ketiye, loma navên livên wê jî bi [[fransî]] ne.|amadekar=Komxebat|sernav=Ansîklopediya Zarokan|cih=Denmark<br>|weşanxane={{w|Weşanên APECl|APEC}}|sal=2004|rûpel=25|isbn=91-89675-32-0}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Hekayêta Betal Kirina '''Xezeta''' Hetawî Kurd|sal=~1910|sala-weşandinê=2026|nivîskar=Mela Seîdê Şemdînanî|amadekar=David N. MacKenzie, Ergin Öpengin|url=http://books.google.com.tr/books?id=eqvrEQAAQBAJ&pg=PA383&dq=%22wan+xezetan%22+OR+%22xezet%C3%AA%22+OR+%22xezetin%22+OR+%22xezetek%C3%AA%22+OR+%22xezeta%22+OR+%22w%C3%AA+xezet%C3%AA%22+OR+%22xezetino%22+OR+%22xezetine%22&hl=&cd=2&source=gbs_api|sernav=Shamdinani Stories I: Kurdish Writings on the History and Culture of Central Kurdistan by Mullah Saʼid Shamdinani|weşanxane=BRILL|ISBN=9789004754430|rûpel=383}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Baş e, '''xezete''' dê bi çi ezmanî derkevit?|nivîskar=E. Rehmî Ji Mala Hekarîyan|amadekar=M. E. Bozarslan|sernav=Jîn: kovara Kurdî-Tirkî. Cild 1|weşanxane=Weşanxana Deng|cih=Uppsala|sala-weşandinê=1985|sal=1918|rûpel=310|isbn=978-91-970702-0-1}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=ligor{{sic}} danezanên '''xezete''' ên Turk hezar insan li kurdistan hatin kuştin ku ji % 70 jin û zaruk kal » pîrbûn|nivîskar=Hesen Hişyar Serdî|sernav=Dîtin û Bîrhatinên Min|weşanxane=Çapxana Emîral|cih=Beyrût|sal=1991|sala-weşandinê=2000|rûpel=380|url=https://bnk.institutkurde.org/images/pdf/CTNPMX4FR6.pdf}}
#* {{jêder-malper/ceribandin|ku|jêgirtin='''Xezeteyên''' Kurdî yên li Stenbolê derdiketin bên îstisnakirin, xezeteyekî millî û wetenî yê ku yekemîn car li Kurdistanê hatîye neşirkirin, ew jî xezeteya Zarê Kirmancî ye, ku min xwendîye.|nivîskar=Seîd Veroj|sernav=Mesûd Fanî (Dr. Mesûd Fanî Bîlgîlî)|websîte=Rûpela nû|url=https://www.rupelanu.com/seid-veroj-mesud-fani-dr-mesud-fani-bilgili-17258h.htm|tarîx=2021-05-04}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=me '''xezete''' da destê wî jî.|sal=2022|nivîskar=Abdurrahman Benek|url=http://books.google.com.tr/books?id=SdSUEAAAQBAJ&pg=PA33&dq=%22wan+xezetan%22+OR+%22xezet%C3%AA%22+OR+%22xezetin%22+OR+%22xezetek%C3%AA%22+OR+%22xezeta%22+OR+%22w%C3%AA+xezet%C3%AA%22+OR+%22xezetino%22+OR+%22xezetine%22&hl=&cd=3&source=gbs_api|sernav=Payiza Mezinan|weşanxane=Nûbihar|ISBN=9786059413749|rûpel=33}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Jêgirtinek ji kitêbekê.|nivîskar=Rojen Barnas|sernav=Sîlvan/ Silîvan / FARQÎN|dîrok=2013|weşanxane=Nûbihar|rûpel=189|isbn= 9786055402884, 6055402882}}
#* {{jêder-malper/ceribandin|ku|nivîskar=Gazete Duvar|sernav=Şairê dubeytan: Baba Tahirê Uryan|websîte=https://www.gazeteduvar.com.tr|url=https://www.gazeteduvar.com.tr/saire-dubeytan-baba-tahire-uryan-haber-1753893|tarîx=2025-02-02|jêgirtin=adfasdf}}
#* {{jêder-kitêb/ceribandin|ku|sernav=Shamdinani Stories I|weşanxane=Brill|sal=2026|isbn=978-90-04-75443-0|url=https://brill.com/edcollbook-oa/title/74006|jêgirtin=test}}
# Maneya 3an.
5e1ib7plxmapch8kgbr1t0of6e2255n
6291564
6291563
2026-07-26T14:09:50Z
Wikihez
7344
6291564
wikitext
text/x-wiki
{{/vê rêzê neguhêre}}
== {{ziman|en}} ==
=== Lêker ===
[[Wêne:Example.jpg|thumb|Mînak]]
{{lêker|en}}
# Maneya 1ê.
#: {{mk|ku|'''Nimûneyeke''' bikaranînê ya vê peyvê.}}
#* {{jêder-kovar/ceribandin|en|nivîskar=Justin Chang|lînka-nivîskar=Justin Chang|sernav=The Day He Arrives [film review]|url=https://variety.com/2011/film/markets-festivals/the-day-he-arrives-1117945326/|urlya-arşîvê=https://web.archive.org/web/20170628015008/https://variety.com/2011/film/markets-festivals/the-day-he-arrives-1117945326/|tarîxa-arşîvê=28 June 2017|kovar=[[w:Variety (magazine)|Variety]]|tarîx=25 May 2011|issn=0042-2738|oclc=960911146|jêgirtin=The outline could apply to most of the self-effacing, increasingly inward-looking comedies written and directed by [[w:Hong Sang-soo|Hong Sang-soo]], and it certainly describes "[[w:The Day He Arrives|The Day He Arrives]]," an agreeably meandering exercise that brings some clever French New Wave '''fillips''' and structural repetitions to Hong's characteristically boozy party.}}
#* {{jêder-kitêb/ceribandin|en|nivîskar=F[rancis] Scott Fitzgerald|lînka-nivîskar=F. Scott Fitzgerald|beş=IX|sernav=[[w:The Great Gatsby|The Great Gatsby]]|cih=New York, N.Y.|weşanxane=[[w:Charles Scribner's Sons|Charles Scribner’s Sons]]|sal=1925|oclc=884653065|cih2=New York, N.Y.|weşanxane2=Charles Scribner’s Sons|sal2=1953|isbn2=978-0-684-16498-4|rûpel2=182|urlya-rûpelê2=https://archive.org/stream/in.ernet.dli.2015.184960/2015.184960.The-Great-Gatsby#page/n185/mode/1up|jêgirtin=So we beat on, boats against the current, borne back '''ceaselessly''' into the past.}}
#* {{jêder-malper/ceribandin|en|nivîskar=Meg Downey|sernav=Marvel’s Comic Soul Stone could Explain the Jump from Infinity War’s Ending to Avengers 4: A Brief History of Crazy Multidimensional Power|url=https://www.polygon.com/2018/5/7/17328230/infinity-war-avengers-4-marvel-comics-soul-stone|urlya-arşîvê=https://web.archive.org/web/20180624110542/https://www.polygon.com/2018/5/7/17328230/infinity-war-avengers-4-marvel-comics-soul-stone|tarîxa-arşîvê=24 June 2018|xebat=[[w:Polygon (website)|Polygon]]|tarîx=7 May 2018|jêgirtin=There’s a very real chance that, rather than crumbling into the dust and floating off into the '''ether''', Thanos’s victims [in the film ''[[w:Avengers: Infinity War|Avengers: Infinity War]]''] were actually sucked up into the Soul Stone.}}
#* {{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=The Republic of Molossia has played a considerable role in developing '''intermicronational''' diplomacy.}}
#* {{jêder-kitêb/ceribandin|en
|sal=c. 1988|nivîskar=Peter W. Rodino, Jr.; et al.|sernav=Report of the Congressional Committees Investigating the Iran/Contra Affair|urlya-rûpelê=http://books.google.com/books?id=ew_K3auTwEgC&pg=PA646&dq=phone+call|rûpel=646|jêgirtin=On November 20, they had a '''phone call''' and later a meeting with others lasting at least 1 1/2 hours.}}
#* {{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=Centrally acting '''sympathicolytics''' cause a decrease in the sympathetic outflow from the brainstem. This reduction in sympathetic outflow brings about vasodilatation of the peripheral vascular bed with a concomitant drop in the blood pressure}}
#* {{jêder-kitêb/ceribandin|en
|sal=2005|edîtor=Martin Endress; George Psathas; Hisashi Nasu|beş=Between the Everyday Life-World and the World of Social Scientific Theory—Towards an “Adequate” Social Thory|nivîskar=Hisashi Nasu|sernav=Explorations of the Life-World: Continuing Dialogues with Alfred Schutz|weşanxane=Springer|isbn=1402032196|rûpel=136|jêgirtin=It is not only the '''man on the street''' who is characterized by taking one's own system of relevance for granted. This holds true for the expert,....}}
#* {{jêder-kitêb/ceribandin|en
|sal=2002|nivîskar=Peter Shirlow|beş=Sinn Féin: Beyond; within Containment|hejmara-beşê=4|edîtor=Stefan Wolff; Jörg Neuheiser|sernav=Peace at Last?: The Impact of the Good Friday Agreement on Northern Ireland|weşanxane=Berghahn Books|isbn=157181518X|urlya-rûpelê=http://books.google.com/books?id=QyInASKCFjcC&pg=PA62&dq=ethnosectarian&sig=Q54T1Vz3vsKnqd05666N1ddFki8|rûpel=62|jêgirtin=Moreover, language, discourse, and dialogue, their definition and mobilisation, create chains of equivalence which enclose subjects within not only '''ethnosectarian''' but at times entirely self-referential and self-sustained notions of identity, practice and collective devotion.}}
#* {{jêder-kitêb/ceribandin|en
|sernav=Extreme Hydrological Events: New Concepts for Security|edîtor=O.F. Vasiliev; P.H.A.J.M. van Gelder; E.J. Plate; M.V. Bolgov|sal=2007|weşanxane=[[w:Springer Science+Business Media|Springer]]|isbn=1402057393|beş=Predetermination of Floods|nivîskar=Pierre Hubert; Ioulia Tchiguirinskaia; Daniel Schertzer; Hocine Bendjoudi; Shaun Lovejoy|qisim=On Probability and Return Periods of Floods|hejmara-qismê=2|rûpel=187|jêgirtin=It is perfectly legitimate to be interested, particularly for safety studies, by millennium floods or floods or '''decamillennium''' floods (i.e. having a probability 0,001 and 0,0001 respectively to occur in the course of given year) of a river which did not exist five thousand years ago and that will not exist perhaps any more in ten thousand years, similarly to the industrial interest in the probability of faults which will not undoubtedly occur, because the minimization of their probability is an aspect of a policy of industrial security.}}
#* {{jêder-kitêb/ceribandin|en
|sal=1995|nivîskar=w:Robert M. Solovay|nivîskarê-sereke=w:Kurt Gödel|edîtor=w:Solomon Feferman; w:John W. Dawson Jr.; w:Warren Goldfarb; {{w|Charles Parsons (philosopher)|Charles Parsons}}; w:Robert M. Solovay|beş=Notes to *1970 [{{w|Godel's ontological proof}}]|sernav=Kurt Gödel: Collected Works: Volume III|urlya-rûpelê=https://books.google.com.au/books?id=gDzbuUwma5MC&pg=PA419&dq=%22Hausdorff+gap%22%7C%22Hausdorff+gaps%22&hl=en&newbks=1&newbks_redir=0&sa=X&ved=2ahUKEwjo-o7D9OT7AhVQJUQIHaSlBIgQ6AF6BAhXEAI#v=onepage&q=%22Hausdorff%20gap%22%7C%22Hausdorff%20gaps%22&f=false|rûpel=419|weşanxane=w:Oxford University Press|jêgirtin=Of course, Hausdorff did not talk of models of set theory or prove absoluteness results. He gave a direct construction in ''ZFC'' of what is now called a '''Hausdorff gap''' and proved properties 1 through 4 for his construction. The notion of a '''Hausdorff gap''' and the proof that '''Hausdorff gaps''' are indestructible under cardinal preserving extensions (property 5 above) are due to Kunen (unpublished).}}
#* {{jêder-kitêb/ceribandin|en
|sal=1925|nivîskar={{w|F. Scott Fitzgerald|F[rancis] Scott Fitzgerald}}|beş=IX|sernav=w:The Great Gatsby|cih=New York, N.Y.|weşanxane=w:Charles Scribner's Sons|oclc=884653065|sal2=1953|cih2=New York, N.Y.|weşanxane2=Charles Scribner’s Sons|isbn2=978-0-684-16498-4|rûpel2=182|urlya-rûpelê2=https://archive.org/stream/in.ernet.dli.2015.184960/2015.184960.The-Great-Gatsby#page/n185/mode/1up|jêgirtin=So we beat on, boats against the current, borne back '''ceaselessly''' into the past.|lccn=gm 95-1}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar=w:John Boyle O'Reilly|beş=On the Trail|sernav=[[w:Moondyne|Moondyne: A Story from the Under-world]]|cih=London|weşanxane=[[w:Routledge|George Routledge and Sons]]|sal=1878|sala-weşandinê=1879|oclc=39983928|sernav2=Moondyne: A Story of Convict Life in Australia|cih2=London|weşanxane2=George Routledge & Sons, Limited, Broadway House, {{w|Ludgate Hill}}|sal2=[1880s]|qisim2=book first|pages2=23–24|urlya-rûpelê2=https://archive.org/stream/moondynestory00oreirich#page/24/mode/1up/|oclc2=83033698|jêgirtin=It was sore travelling for horse and man under the blazing sun, with no food nor water save what he pressed from the pith of the palms, and even these were growing scarce. The only life on the plains was the hard and dusty scrub. Every hour brought a more hopeless and '''grislier''' desolation.}}
#* {{jêder-kitêb/ceribandin|en
|sal=1966|sala-orîjînal=1858|nivîskar=w:Hans Christian Andersen|terciman=Pat Shaw Iversen|orîjînal=Suppe paa en Pølsepind|zimanê-orîjînal=da|beş=Soup from a Sausage Peg|sernav=The Snow Queen and Other Tales|serî=Signet Classic|cildê-seriyê=CT334|cih=New York, N.Y.|weşanxane=w:New American Library|rûpel=224|urlya-rûpelê=http://books.google.com/books?id=2tjWAAAAMAAJ&q=%22from+your+own+nook+and+cranny%22#search_anchor|oclc=636818779|metna-orîjînal=da:Det er underligt at komme hjemme fra sin egen '''Krinkelkrog''', gaae med Skib, der ogsaa er en Slags '''Krinkelkrog''', og saa pludselig være over hundrede Mile borte og staae i et fremmedt Land.|jêgirtin=It’s strange to come away from home, from your own '''nook and cranny''', to go by ship – which is also a kind of '''nook and cranny''' – and then suddenly be more than a hundred miles away and stand in a foreign land!}}
#* {{jêder-kitêb/ceribandin|en
|sal=2013|nivîskar=Bernd Klauer; Reiner Manstetten; Thomas Petersen; Johannes Schiller|zimanê-eserê=de|beş=Materielle Bestände|sernav=Die Kunst, langfristig zu denken : Wege zur Nachhaltigkeit<t:The art of thinking long-term : paths to sustainability>|cih=Baden-Baden|weşanxane=Nomos|versiyona-nû=translated as|terciman2=Kathleen Cross|sal2=2016|sernav2=Sustainability and the Art of Long-Term Thinking|beş2=Material stocks|cih2=London; New York|weşanxane2=Routledge, Taylor & Francis Group|qisim2=4.3.1. Material sets and stocks|rûpel2=44|isbn2=9781134986187|jêgirtin=The property of '''appertainment''' of a set is therefore the property which, by definition, has to be given in every element of the set.}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar={{w|Andrew Seth Pringle-Pattison|Andrew Seth}}|sernav=Hegelianism and Personality|çap=2nd|sal=1887|sala-weşandinê=1893|rûpel=216|rêz-sade=footnote|cih=Edinburgh; London|weşanxane=William Blackwood and Sons|versiyona-nû=quoted in|sal2=1893|2nivîskar=w:David George Ritchie|sernav2=Darwin and Hegel, with other Philosophical Studies|cih2=London|weşanxane2=S. Sonnenschein & Co.; New York, N.Y.: [[w:Macmillan Publishers (United States)|Macmillan & Co.]]|rûpel2=72|urlya-rûpelê2=http://books.google.com/books?id=8yGEAHvAwiIC&q=%22An+absolute+system+cannot+afford+to+leave+any+nook+or+cranny+of+existence+unexplored%22#search_anchor|rêz-sade2=note 1|oclc2=3299658|jêgirtin=An absolute system cannot afford to leave '''any nook or cranny''' of existence unexplored.}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar=w:David George Ritchie|jê-tê-girtin=w:Andrew Seth Pringle-Pattison|sernav=Hegelianism and Personality|çap=2nd|kesên-din=1893, page 216, footnote|cihê-jêgirtinê=Darwin and Hegel, with other Philosophical Studies|cih=London|weşanxane=S. Sonnenschein & Co.; New York, N.Y.: [[w:Macmillan Publishers (United States)|Macmillan & Co.]]|sal=1893|rûpel=72|rêz-sade=note 1|urlya-rûpelê=http://books.google.com/books?id=8yGEAHvAwiIC&q=%22An+absolute+system+cannot+afford+to+leave+any+nook+or+cranny+of+existence+unexplored%22#search_anchor|oclc=3299658|jêgirtin=An absolute system cannot afford to leave '''any nook or cranny''' of existence unexplored.}}
#* {{jêder-kovar/ceribandin|en
|nivîskar=w:Hayden Carruth|sernav=Making It New|kovar=w:The Hudson Review|cih=New York, N.Y.|weşanxane=Hudson Review, Inc.|meh=Summer|sal=1968|cild=XXI|hejmar=2|issn=2325-5935|oclc=920393805|versiyona-nû=reprinted as|cure2=book|beş2=From ‘Making It New’ [''Body Rags'']|edîtor2=Howard Nelson|sernav2=On the Poetry of Galway Kinnell: The Wages of Dying|serî2=Under Discussion|cih2=Ann Arbor, Mich.|weşanxane2={{w|University of Michigan Press}}|sal2=1987|rûpel2=75|urlya-rûpelê2=https://books.google.com/books?id=7cXV26PmJUYC&pg=PA75|isbn2=978-0-472-09376-2|jêgirtin=In his [{{w|Galway Kinnell}}'s] new book, ''Body Rags'', he has brought this style to a kind of perfection, especially in two poems about the killing of animals, "The Porcupine" and "The Bear." These are the '''grisliest''' poems I have ever read.}}
#* {{jêder-kitêb/ceribandin|fr
|sal=1973|nivîskar=w:Claude Simon|sernav=Tryptique|weşanxane=Éditions de Minuit|rûpel=12|jêgirtin=Les sons de la cloche '''égrenant''' les quarts, les demies et les heures {{...}}|t=The sounds of the clock '''rattling off''' the quarters, the halves and the hours {{...}}}}
#* {{jêder-kitêb/ceribandin|de
|sal=1999|nivîskar=Paul Hoffmann|hevnivîskar=Stefan H. Brandenburger; Ulrike Brauner; Thomas Hieke|sernav=Q 22:28, 30: You Will Judge the Twelve Tribes of Israel|serî=Documenta Q: Reconstructions of Q Through Two Centuries of Gospel Research Excerpted, Sorted and Evaluated|cildê-seriyê=volume 5|isbn=90-429-0734-7|rûpel=189|jêgirtin=Zumal in beiden Fällen eine Partizipialform vorliegt, liegt es nahe, die '''Zeitworte''' als Varianten anzusehen.}}
#* {{jêder-kitêb/ceribandin|ru
|nivîskar=lw:ru:[[Филарет (Дроздов)|Митрополи́т Филаре́т]]<tr:-><t:Mitropolit Filaret>|beş=ru:Сло́во предъ приведеніемъ къ присягѣ Санктпетербурксаго дворя́нства и гражданъ для избранія суде́й|tarîx=1818-02-01<!--Converted to Gregorian-->|sala-weşandinê=1873|cih=Moscow|sernav=ru:Сочинеиія Филарета Митрополита Московскаго и Коломенскаго|cild=I|rûpel=253|urlya-rûpelê=https://books.google.com/books?id=yWEcAAAAMAAJ&pg=PA253|jêgirtin=[[изъ|Изъ]] [[сей|сего́]] [[ви́дно]], [[что]] [[судья|судія́]], [[как|какъ]] [[блюсти́тель]] [[общественный|обще́ственнаго]] [[порядок|поря́дка]], [[должен|долженъ]] '''объима́ть''' [[и]] [[проника́ть]] [[мысль|мы́слію]] [[весь]] [[состав|соста́въ]] [[общество|о́бщества]];|t=From this it becomes discernible that a judge, in his function as a warden of public order, should '''enclose''' and traverse the whole body of the society;}}
#* {{jêder-kitêb/ceribandin|yi
|nivîskar=I.J. Singer|sernav=Fun a velt vos iz nishto mer|sal=1946|beş=yi:אַ ייד אַ מלמד פֿאַרגלוסט זיך צו ווערן אַ מלאך אום פּורים און פֿאַרלירט, נעבעך, אַן אויג<subst:ייד/ייִד,מלמד/מעלאַמעד,פּורים/פּורעם,מלאך/מאַלעך><t:A Jew, a teacher, decides to become an angel on Purim and, unfortunately, loses an eye>|jêgirtin=יעדן '''זמן''' אויפֿסניי, חול־המועד סוכות אָדער חול־המועד פּסח, בין איך געווען זייער האָפֿערדיק, וואָס מען האָט מיך גענומען איין מלמד און אָנגעדונגען צו אַן אַנדערן, גלײבנדיק, אַז דער נייער וועט זיין בעסער ווי דער אַלטער.|subst=זמן//זמאַן,חול־המועד//כאַלעמויעד,סוכות//סוקוס,פּסח//פּייסעך,מלמד//מעלאַמעד,נייער//נײַער|tercime=Every '''term''' anew, during the intermediary periods of Sukkot and Pesach, I was very confident in being taken from one melamed and given to another, believing the new one would be better than the old.}}
#* {{jêder-kitêb/ceribandin|ru
|sal=1983|nivîskar=w:Samuil Marshak|sernav=Баллада о королевском бутерброде<subst:бутер/бутэр><t:Ballad of the King's Bread>|orîjînal=The King's Breakfast|ji-aliyê=w:A. A. Milne|metna-orîjînal=en:"Nobody," / He whimpered, / "Could call me / A fussy man; / I only want / A little bit / Of butter for / My bread!"|zimanê-orîjînal=en|jêgirtin=— [[никто|Никто́]] [[не]] [[сказать|ска́жет]], [[бу́дто]] [[я]] / [[тиран|Тира́н]] [[и]] [[сумасбро́д]], / [[за|За]] [[то́]], [[что]] [[к]] [[чай|ча́ю]] [[я]] [[любить|люблю́]] / [[хороший|Хоро́ший]] '''бутербро́д'''.|subst=бутербро́д/бутэрбро́д|t="No one would say that I / Am a tyrant and madman, / For the fact that with my tea I like / A good '''bread-and-butter sandwich'''."}}
#* {{jêder-kitêb/ceribandin|ota
|sal=1922|sernav=ota:ادارهٔ حرب و سیاست<tr:Idare-i harb ve siyaset>|cih=Istanbul|weşanxane=ota:مطبعهٔ عثمانیه|rûpel=176|urlya-rûpelê=https://babel.hathitrust.org/cgi/pt?id=uc1.aa0006749931&view=1up&seq=182|versiyona-nû=translation of|ziman2=de|sal2=1922|2nivîskar=w:Erich Ludendorff|sernav2=Kriegsführung und Politik|cih2=Berlin|weşanxane2=E. S. Mittler & Sohn|rûpel2=199|urlya-rûpelê2=https://archive.org/details/kriegfhrungund00lude/page/199|jêgirtin=شرقی غالیچیا، و بوقوونیاده شمندوفر نقاباتی تأسس ایتدكدن صكره شرقده '''صلحی''' تأمین ایچون دینیهستر نهری جنوبندن روس — رومن جبههسنه قارشو حربه دوام ایتمك فكرندن فراغت ایتمك لازمكلیورایدی. بر قصقاچ تأثیری یاپهبیلمك ایچون بری قلاص غربندن یوقارهسهرهت اوزرینه و دیكری دینیهستر جنوبندن اولمق اوزره مضاعف بر تعرض اجراسی لازمكلیور ایده.|tr=Şarki Galiçya, ve Bukovinyada şömendüfer nakabatı tesis itdikden soñra şarkda sulhı temin içün Dinyester nehri cenubundan rus-rumen cebhesine karşu harbe devam itmek fikrinden feragat itmek lazım geliyor idi. Bir kıskaç tesiri yapabilmek içün biri Kalas garbından yukara-yı Seret üzerine ve diğeri Dinyester cenubundan olmak üzere muzaaf bir taarruz icrası lazım geliyor idi.|metna-orîjînal=de:Unsere weitere Absicht, nach Herstellung der Eisenbahnverbindungen in Ostgalizien und in der Bukowina zur Erzwingung des Friedens im Osten den Feldzug gegen die russisch-rumänische Front südlich des Dnjestr fortzusetzen, mußte aufgegeben werden. Er sollte in einem doppelten Angriff über den oberen Sereth westlich Galatz und hart südlich des Dnjestr bestehen und auch zangenartig wirken.|t=Our further intention, the military campaign against the Russian-Romanian front to be continued south of the Dniestr to force '''peace''' in the east, after procurement of the railroad connections in Eastern Galicia and in the Bukovina, had to be abandoned. It was supposed to consist in a double attack over the upper Siret west of Galați as well as hard south of the Dniestr and go like pincers.}}
#* {{jêder-kitêb/ceribandin|it
|sernav=L'œuvre complète de Tchouang-tseu|terciman=Liou Kia-hway<f:Hant:劉家槐><f:Pinyin:Liú Jiāhuái>|orîjînal={{w|Zhuangzi (book)|Zhuang-zi}}|ji-aliyê=莊周<t:Zhuang Zhou>|zimanê-orîjînal=och|sala-orîjînal=c. 300 {{BCE}}|sal=1969|sal2=1982|sala-weşandinê2=2010|versiyona-nû=translated as|terciman2=Carlo Laurenti; Christine Leverd|sernav2=Zhuang-zi [Chuang-tzu]|serî2=Gli Adelphi|cildê-seriyê2=41|beş2=Libertà naturale|tercimeya-beşê2=Natural Freedom|çap2=6th|cih2=Milan|weşanxane2={{w|Adelphi Edizioni}}|rûpel2=17|isbn2=978-88-459-0950-4|metna-orîjînal=och:之人也,物莫之傷,大浸稽天而不溺,大旱、金石流、土山焦而不熱。|jêgirtin=Se le acque si alzassero fino al cielo, non annegherebbero. Se la siccità '''liquefacesse''' i metalli e infiammasse le montagne, non ne sarebbero neppure sfiorati.|t=If the waters were to rise to the skies, they wouldn't drown. If drought were to '''liquefy''' metals, and set fire to the mountains, they wouldn't even be touched by it.}}
#* {{jêder-kitêb/ceribandin|fr
|zimanê-termê=en|sal=1677|nivîskar=Guy Miege [''i.e.'', [[w:fr:Guy Miège|Guy Miège]]]|made=HAGGESS|urlya-madeyê=https://books.google.com/books?id=YbWtTwVbLioC&pg=PP857|sernav=A New Dictionary French and English, with Another English and French; According to the Present Use, and Modern Orthography of the French. Inrich’d with New Words, Choice Phrases, and Apposite Proverbs; Digested into a Most Accurate Method; and Contrived for the Use Both of English and Foreiners|cih=London|weşanxane=Printed by Tho[mas] Dawks, for Thomas Basset, at the George, near [[w:Clifford's Inn|Cliffords-inn]], in [[w:Fleet Street|Fleetstreet]]|stûn=3|oclc=874955677|jêgirtin='''HAGGESS''', '''haggas''', or '''haggis''', ſort d'appret fait avec des herbes, du larde caupé menu, des épices, des œufs, & du fromage, le tout mis enſemble & bouilli dans une panſe de brebis.|t='''HAGGESS''', '''haggas''', or '''haggis''', a sort of preparation made with herbs, bacon, spices, eggs, cheese, all put together and boiled in a sheep's belly.|brackets=1}}
#*{{jêder-kovar/ceribandin|en|sernav=Art VIII.—Who are the True Conservatives?|kovar=The Quarterly Christian Spectator|cih=New Haven, Conn.|weşanxane=Published for the proprietor by Hezekiah Howe [...]|meh=November|sal=1838|cild=X|hejmar=IV|rûpel=608|urlya-rûpelê=https://books.google.com/books?id=S7MAAAAAMAAJ&pg=PA608|oclc=5090641|jêgirtin=They have guarded a few minds against '''sciolism''' and pretension in philosophy, and they have given a new and more imposing air to '''sciolism''' itself, and placed in the hands of the charlatan enchantments and magical arts, which hold fast its victims with a potency never granted to an inferior spell. There is no '''sciolism''' now which is more dangerous than that which is so very careful to inform us of its entire freedom from all that is superficial, {{...}}}}
#*{{jêder-kovar/ceribandin|en
|tarîx=March 8, 2023|paşnav=Wu|pêşnav=Huizhong|nivîskar2=Johnson Lai|sernav=Taiwan suspects Chinese ships cut islands’ internet cables|kovar={{w|The Washington Post}}|urlya-arşîvê=https://web.archive.org/web/20230311220100/https://www.washingtonpost.com/business/2023/03/08/matsu-taiwan-internet-cables-cut-china/d322fcae-bd70-11ed-9350-7c5fccd598ad_story.html|tarîxa-arşîvê=11 March 2023|issn=0190-8286|oclc=2269358|qisim=Business|tarîx2=April 18, 2023|sernav2=Chinese ships cut internet of Taiwan’s outlying islands|cure2=journal|kovar2={{w|AP News}}|urlya-arşîvê2=https://web.archive.org/web/20230308053048/https://apnews.com/article/matsu-taiwan-internet-cables-cut-china-65f10f5f73a346fa788436366d7a7c70|jêgirtin=Wang Chung Ming, the head of '''Lienchiang''' County, as the Matsu islands are officially called, said he and the legislator from Matsu went to Taipei shortly after the internet broke down to ask for help, and was told they would get priority in any future internet backup plans.{{...}}<br>Wang, the head of '''Lienchiang''' County, said he had mentioned the cables on a recent visit to China, where he had met an executive from China Mobile.}}
#*{{jêder-kovar/ceribandin|en
|sal=1994|meh=April|kovar=光華|tercimeya-kovar=Sinorama|url=https://books.google.com/books?id=GIRDAAAAYAAJ|cild=19|hejmar=1-6|weşanxane={{w|Government Information Office}}|issn=0256-9043|oclc=20549278|sal2=1994|meh2=April|2nivîskar=Yuan-ching Cheng|cure2=journal|sernav2=Kinmen- -From Battlefield to Tourist Destination|kovar2=台灣光華雜誌<t:Taiwan Panorama>|terciman2=Robert Taylor|urlya-arşîvê2=https://web.archive.org/web/20221021103917/https://www.taiwan-panorama.com/en/Articles/Details?Guid=c9335c12-7ec3-40e7-96be-33a81416a223|tarîxa-arşîvê2=21 October 2022|qisim2=Around Taiwan|jêgirtin=The temple to Colonel Li Kuang-chien lies near Hsiputou village in the northwest of Kinmen Island, around five kilometers from the town of '''Chincheng''' and next to the famous Hill 132.{{...}}<br>Because Kinmen was settled early, it bears many traces of the past. There are 21 listed historical monuments scattered all over the island. The only Grade I monument among them is the "Chiehhsiao Arch," commemorating Chiu Liang-kung's mother, located in East Houpu Street in the center of '''Chincheng'''.}}
== {{ziman|ku}} ==
=== Lêker ===
[[Wêne:Example.jpg|thumb|Mînak]]
{{lêker|ku}}
# [[testsdfsdafdasfas]]
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Mîr Kamûran di sala 1947 an de li '''Fransayê''' bicîh bû, û heta 1970ê li Xwendegeha zimanên rojhilatî ya Zaningeha Parîsê Profesorîya ziman û çanda Kurdî kir.|nivîskar=Kamuran A.Bedir Khan|nivîskar2=Josefe Bertolino|nivîskar3=Kendal Nezan|sernav=Institut Kurde De Paris Ferhenga Kurdî-Fransizî|cih=Istanbul|weşanxane={{w|Weşanên Avesta|Avesta}}|sal=2017|çap=1|rûpel=XXXIX|isbn=978-2-360134311}}
#* {{jêder-malper/ceribandin|ku|jêgirtin=Li gor amareke ajanseke selametiya xorakê, xelkê '''Fransa''' salane nêzîkî 4,000 ton Beqan dixwin, ku ew jî bi qasî goştê 40 nehengên şîn e. Ji bo dabînkirina pêwîstiya bazarê jî, li '''Fransayê''' kêlgeh û cihên taybet ên xwedîkirina Beqan hene. Beşek ji wêneyên di vê wênenûçeyê de, kêlgeha xwedîkirina Beqan li navçeyeke '''Fransayê''' ye, herwiha beşek ji wêneyan jî awayê patin û çêkirina xwarinê ji goştê Beqan di xwaringehan de û pêşkêşkirina wan bi mêvanan nîşan didin.|nivîskar=ciwan ikbal|sernav=Fransî di salekê de 4,000 ton Beqan dixwin|url=https://www.rudaw.net/kurmanci/world/101020201|dîrok=10.10.2020}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Bale awayeke dansê ye. Babetek ji şanoya musîkal e. Ji bo dansa baleyê, divê meriv cansivik û nerm be. Loma jî bale gelekî antremanê dixwaze û divê meriv di temenekî biçûk de dest pê bike. Di baleyê de, meriv li gor livên diyarkirî dansê dike. Hinek ji wan tu di wêne de dibînî. Ji ber ku bale li '''Fransayê''' bi pêş ketiye, loma navên livên wê jî bi [[fransî]] ne.|amadekar=Komxebat|sernav=Ansîklopediya Zarokan|cih=Denmark<br>|weşanxane={{w|Weşanên APECl|APEC}}|sal=2004|rûpel=25|isbn=91-89675-32-0}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Hekayêta Betal Kirina '''Xezeta''' Hetawî Kurd|sal=~1910|sala-weşandinê=2026|nivîskar=Mela Seîdê Şemdînanî|amadekar=David N. MacKenzie, Ergin Öpengin|url=http://books.google.com.tr/books?id=eqvrEQAAQBAJ&pg=PA383&dq=%22wan+xezetan%22+OR+%22xezet%C3%AA%22+OR+%22xezetin%22+OR+%22xezetek%C3%AA%22+OR+%22xezeta%22+OR+%22w%C3%AA+xezet%C3%AA%22+OR+%22xezetino%22+OR+%22xezetine%22&hl=&cd=2&source=gbs_api|sernav=Shamdinani Stories I: Kurdish Writings on the History and Culture of Central Kurdistan by Mullah Saʼid Shamdinani|weşanxane=BRILL|ISBN=9789004754430|rûpel=383}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Baş e, '''xezete''' dê bi çi ezmanî derkevit?|nivîskar=E. Rehmî Ji Mala Hekarîyan|amadekar=M. E. Bozarslan|sernav=Jîn: kovara Kurdî-Tirkî. Cild 1|weşanxane=Weşanxana Deng|cih=Uppsala|sala-weşandinê=1985|sal=1918|rûpel=310|isbn=978-91-970702-0-1}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=ligor{{sic}} danezanên '''xezete''' ên Turk hezar insan li kurdistan hatin kuştin ku ji % 70 jin û zaruk kal » pîrbûn|nivîskar=Hesen Hişyar Serdî|sernav=Dîtin û Bîrhatinên Min|weşanxane=Çapxana Emîral|cih=Beyrût|sal=1991|sala-weşandinê=2000|rûpel=380|url=https://bnk.institutkurde.org/images/pdf/CTNPMX4FR6.pdf}}
#* {{jêder-malper/ceribandin|ku|jêgirtin='''Xezeteyên''' Kurdî yên li Stenbolê derdiketin bên îstisnakirin, xezeteyekî millî û wetenî yê ku yekemîn car li Kurdistanê hatîye neşirkirin, ew jî xezeteya Zarê Kirmancî ye, ku min xwendîye.|nivîskar=Seîd Veroj|sernav=Mesûd Fanî (Dr. Mesûd Fanî Bîlgîlî)|websîte=Rûpela nû|url=https://www.rupelanu.com/seid-veroj-mesud-fani-dr-mesud-fani-bilgili-17258h.htm|tarîx=2021-05-04}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=me '''xezete''' da destê wî jî.|sal=2022|nivîskar=Abdurrahman Benek|url=http://books.google.com.tr/books?id=SdSUEAAAQBAJ&pg=PA33&dq=%22wan+xezetan%22+OR+%22xezet%C3%AA%22+OR+%22xezetin%22+OR+%22xezetek%C3%AA%22+OR+%22xezeta%22+OR+%22w%C3%AA+xezet%C3%AA%22+OR+%22xezetino%22+OR+%22xezetine%22&hl=&cd=3&source=gbs_api|sernav=Payiza Mezinan|weşanxane=Nûbihar|ISBN=9786059413749|rûpel=33}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Jêgirtinek ji kitêbekê.|nivîskar=Rojen Barnas|sernav=Sîlvan/ Silîvan / FARQÎN|sal=2013|weşanxane=Nûbihar|rûpel=189|isbn= 9786055402884, 6055402882}}
#* {{jêder-malper/ceribandin|ku|nivîskar=Gazete Duvar|sernav=Şairê dubeytan: Baba Tahirê Uryan|websîte=https://www.gazeteduvar.com.tr|url=https://www.gazeteduvar.com.tr/saire-dubeytan-baba-tahire-uryan-haber-1753893|tarîx=2025-02-02|jêgirtin=adfasdf}}
#* {{jêder-kitêb/ceribandin|ku|sernav=Shamdinani Stories I|weşanxane=Brill|sal=2026|isbn=978-90-04-75443-0|url=https://brill.com/edcollbook-oa/title/74006|jêgirtin=test}}
# Maneya 3an.
r9qsk0xbowatxlsiv0ra8t9f9bcuz6m
6291565
6291564
2026-07-26T14:31:39Z
Wikihez
7344
6291565
wikitext
text/x-wiki
{{/vê rêzê neguhêre}}
== {{ziman|en}} ==
=== Lêker ===
[[Wêne:Example.jpg|thumb|Mînak]]
{{lêker|en}}
# Maneya 1ê.
#: {{mk|ku|'''Nimûneyeke''' bikaranînê ya vê peyvê.}}
#* {{jêder-kovar/ceribandin|en|nivîskar=Justin Chang|lînka-nivîskar=Justin Chang|sernav=The Day He Arrives [film review]|url=https://variety.com/2011/film/markets-festivals/the-day-he-arrives-1117945326/|urlya-arşîvê=https://web.archive.org/web/20170628015008/https://variety.com/2011/film/markets-festivals/the-day-he-arrives-1117945326/|tarîxa-arşîvê=28 June 2017|kovar=[[w:Variety (magazine)|Variety]]|tarîx=25 May 2011|issn=0042-2738|oclc=960911146|jêgirtin=The outline could apply to most of the self-effacing, increasingly inward-looking comedies written and directed by [[w:Hong Sang-soo|Hong Sang-soo]], and it certainly describes "[[w:The Day He Arrives|The Day He Arrives]]," an agreeably meandering exercise that brings some clever French New Wave '''fillips''' and structural repetitions to Hong's characteristically boozy party.}}
#* {{jêder-kitêb/ceribandin|en|nivîskar=F[rancis] Scott Fitzgerald|lînka-nivîskar=F. Scott Fitzgerald|beş=IX|sernav=[[w:The Great Gatsby|The Great Gatsby]]|cih=New York, N.Y.|weşanxane=[[w:Charles Scribner's Sons|Charles Scribner’s Sons]]|sal=1925|oclc=884653065|cih2=New York, N.Y.|weşanxane2=Charles Scribner’s Sons|sal2=1953|isbn2=978-0-684-16498-4|rûpel2=182|urlya-rûpelê2=https://archive.org/stream/in.ernet.dli.2015.184960/2015.184960.The-Great-Gatsby#page/n185/mode/1up|jêgirtin=So we beat on, boats against the current, borne back '''ceaselessly''' into the past.}}
#* {{jêder-malper/ceribandin|en|nivîskar=Meg Downey|sernav=Marvel’s Comic Soul Stone could Explain the Jump from Infinity War’s Ending to Avengers 4: A Brief History of Crazy Multidimensional Power|url=https://www.polygon.com/2018/5/7/17328230/infinity-war-avengers-4-marvel-comics-soul-stone|urlya-arşîvê=https://web.archive.org/web/20180624110542/https://www.polygon.com/2018/5/7/17328230/infinity-war-avengers-4-marvel-comics-soul-stone|tarîxa-arşîvê=24 June 2018|xebat=[[w:Polygon (website)|Polygon]]|tarîx=7 May 2018|jêgirtin=There’s a very real chance that, rather than crumbling into the dust and floating off into the '''ether''', Thanos’s victims [in the film ''[[w:Avengers: Infinity War|Avengers: Infinity War]]''] were actually sucked up into the Soul Stone.}}
#* {{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=The Republic of Molossia has played a considerable role in developing '''intermicronational''' diplomacy.}}
#* {{jêder-kitêb/ceribandin|en
|sal=c. 1988|nivîskar=Peter W. Rodino, Jr.; et al.|sernav=Report of the Congressional Committees Investigating the Iran/Contra Affair|urlya-rûpelê=http://books.google.com/books?id=ew_K3auTwEgC&pg=PA646&dq=phone+call|rûpel=646|jêgirtin=On November 20, they had a '''phone call''' and later a meeting with others lasting at least 1 1/2 hours.}}
#* {{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=Centrally acting '''sympathicolytics''' cause a decrease in the sympathetic outflow from the brainstem. This reduction in sympathetic outflow brings about vasodilatation of the peripheral vascular bed with a concomitant drop in the blood pressure}}
#* {{jêder-kitêb/ceribandin|en
|sal=2005|edîtor=Martin Endress; George Psathas; Hisashi Nasu|beş=Between the Everyday Life-World and the World of Social Scientific Theory—Towards an “Adequate” Social Thory|nivîskar=Hisashi Nasu|sernav=Explorations of the Life-World: Continuing Dialogues with Alfred Schutz|weşanxane=Springer|isbn=1402032196|rûpel=136|jêgirtin=It is not only the '''man on the street''' who is characterized by taking one's own system of relevance for granted. This holds true for the expert,....}}
#* {{jêder-kitêb/ceribandin|en
|sal=2002|nivîskar=Peter Shirlow|beş=Sinn Féin: Beyond; within Containment|hejmara-beşê=4|edîtor=Stefan Wolff; Jörg Neuheiser|sernav=Peace at Last?: The Impact of the Good Friday Agreement on Northern Ireland|weşanxane=Berghahn Books|isbn=157181518X|urlya-rûpelê=http://books.google.com/books?id=QyInASKCFjcC&pg=PA62&dq=ethnosectarian&sig=Q54T1Vz3vsKnqd05666N1ddFki8|rûpel=62|jêgirtin=Moreover, language, discourse, and dialogue, their definition and mobilisation, create chains of equivalence which enclose subjects within not only '''ethnosectarian''' but at times entirely self-referential and self-sustained notions of identity, practice and collective devotion.}}
#* {{jêder-kitêb/ceribandin|en
|sernav=Extreme Hydrological Events: New Concepts for Security|edîtor=O.F. Vasiliev; P.H.A.J.M. van Gelder; E.J. Plate; M.V. Bolgov|sal=2007|weşanxane=[[w:Springer Science+Business Media|Springer]]|isbn=1402057393|beş=Predetermination of Floods|nivîskar=Pierre Hubert; Ioulia Tchiguirinskaia; Daniel Schertzer; Hocine Bendjoudi; Shaun Lovejoy|qisim=On Probability and Return Periods of Floods|hejmara-qismê=2|rûpel=187|jêgirtin=It is perfectly legitimate to be interested, particularly for safety studies, by millennium floods or floods or '''decamillennium''' floods (i.e. having a probability 0,001 and 0,0001 respectively to occur in the course of given year) of a river which did not exist five thousand years ago and that will not exist perhaps any more in ten thousand years, similarly to the industrial interest in the probability of faults which will not undoubtedly occur, because the minimization of their probability is an aspect of a policy of industrial security.}}
#* {{jêder-kitêb/ceribandin|en
|sal=1995|nivîskar=w:Robert M. Solovay|nivîskarê-sereke=w:Kurt Gödel|edîtor=w:Solomon Feferman; w:John W. Dawson Jr.; w:Warren Goldfarb; {{w|Charles Parsons (philosopher)|Charles Parsons}}; w:Robert M. Solovay|beş=Notes to *1970 [{{w|Godel's ontological proof}}]|sernav=Kurt Gödel: Collected Works: Volume III|urlya-rûpelê=https://books.google.com.au/books?id=gDzbuUwma5MC&pg=PA419&dq=%22Hausdorff+gap%22%7C%22Hausdorff+gaps%22&hl=en&newbks=1&newbks_redir=0&sa=X&ved=2ahUKEwjo-o7D9OT7AhVQJUQIHaSlBIgQ6AF6BAhXEAI#v=onepage&q=%22Hausdorff%20gap%22%7C%22Hausdorff%20gaps%22&f=false|rûpel=419|weşanxane=w:Oxford University Press|jêgirtin=Of course, Hausdorff did not talk of models of set theory or prove absoluteness results. He gave a direct construction in ''ZFC'' of what is now called a '''Hausdorff gap''' and proved properties 1 through 4 for his construction. The notion of a '''Hausdorff gap''' and the proof that '''Hausdorff gaps''' are indestructible under cardinal preserving extensions (property 5 above) are due to Kunen (unpublished).}}
#* {{jêder-kitêb/ceribandin|en
|sal=1925|nivîskar={{w|F. Scott Fitzgerald|F[rancis] Scott Fitzgerald}}|beş=IX|sernav=w:The Great Gatsby|cih=New York, N.Y.|weşanxane=w:Charles Scribner's Sons|oclc=884653065|sal2=1953|cih2=New York, N.Y.|weşanxane2=Charles Scribner’s Sons|isbn2=978-0-684-16498-4|rûpel2=182|urlya-rûpelê2=https://archive.org/stream/in.ernet.dli.2015.184960/2015.184960.The-Great-Gatsby#page/n185/mode/1up|jêgirtin=So we beat on, boats against the current, borne back '''ceaselessly''' into the past.|lccn=gm 95-1}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar=w:John Boyle O'Reilly|beş=On the Trail|sernav=[[w:Moondyne|Moondyne: A Story from the Under-world]]|cih=London|weşanxane=[[w:Routledge|George Routledge and Sons]]|sal=1878|sala-weşandinê=1879|oclc=39983928|sernav2=Moondyne: A Story of Convict Life in Australia|cih2=London|weşanxane2=George Routledge & Sons, Limited, Broadway House, {{w|Ludgate Hill}}|sal2=[1880s]|qisim2=book first|pages2=23–24|urlya-rûpelê2=https://archive.org/stream/moondynestory00oreirich#page/24/mode/1up/|oclc2=83033698|jêgirtin=It was sore travelling for horse and man under the blazing sun, with no food nor water save what he pressed from the pith of the palms, and even these were growing scarce. The only life on the plains was the hard and dusty scrub. Every hour brought a more hopeless and '''grislier''' desolation.}}
#* {{jêder-kitêb/ceribandin|en
|sal=1966|sala-orîjînal=1858|nivîskar=w:Hans Christian Andersen|terciman=Pat Shaw Iversen|orîjînal=Suppe paa en Pølsepind|zimanê-orîjînal=da|beş=Soup from a Sausage Peg|sernav=The Snow Queen and Other Tales|serî=Signet Classic|cildê-seriyê=CT334|cih=New York, N.Y.|weşanxane=w:New American Library|rûpel=224|urlya-rûpelê=http://books.google.com/books?id=2tjWAAAAMAAJ&q=%22from+your+own+nook+and+cranny%22#search_anchor|oclc=636818779|metna-orîjînal=da:Det er underligt at komme hjemme fra sin egen '''Krinkelkrog''', gaae med Skib, der ogsaa er en Slags '''Krinkelkrog''', og saa pludselig være over hundrede Mile borte og staae i et fremmedt Land.|jêgirtin=It’s strange to come away from home, from your own '''nook and cranny''', to go by ship – which is also a kind of '''nook and cranny''' – and then suddenly be more than a hundred miles away and stand in a foreign land!}}
#* {{jêder-kitêb/ceribandin|en
|sal=2013|nivîskar=Bernd Klauer; Reiner Manstetten; Thomas Petersen; Johannes Schiller|zimanê-eserê=de|beş=Materielle Bestände|sernav=Die Kunst, langfristig zu denken : Wege zur Nachhaltigkeit<t:The art of thinking long-term : paths to sustainability>|cih=Baden-Baden|weşanxane=Nomos|versiyona-nû=translated as|terciman2=Kathleen Cross|sal2=2016|sernav2=Sustainability and the Art of Long-Term Thinking|beş2=Material stocks|cih2=London; New York|weşanxane2=Routledge, Taylor & Francis Group|qisim2=4.3.1. Material sets and stocks|rûpel2=44|isbn2=9781134986187|jêgirtin=The property of '''appertainment''' of a set is therefore the property which, by definition, has to be given in every element of the set.}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar={{w|Andrew Seth Pringle-Pattison|Andrew Seth}}|sernav=Hegelianism and Personality|çap=2nd|sal=1887|sala-weşandinê=1893|rûpel=216|rêz-sade=footnote|cih=Edinburgh; London|weşanxane=William Blackwood and Sons|versiyona-nû=quoted in|sal2=1893|2nivîskar=w:David George Ritchie|sernav2=Darwin and Hegel, with other Philosophical Studies|cih2=London|weşanxane2=S. Sonnenschein & Co.; New York, N.Y.: [[w:Macmillan Publishers (United States)|Macmillan & Co.]]|rûpel2=72|urlya-rûpelê2=http://books.google.com/books?id=8yGEAHvAwiIC&q=%22An+absolute+system+cannot+afford+to+leave+any+nook+or+cranny+of+existence+unexplored%22#search_anchor|rêz-sade2=note 1|oclc2=3299658|jêgirtin=An absolute system cannot afford to leave '''any nook or cranny''' of existence unexplored.}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar=w:David George Ritchie|jê-tê-girtin=w:Andrew Seth Pringle-Pattison|sernav=Hegelianism and Personality|çap=2nd|kesên-din=1893, page 216, footnote|cihê-jêgirtinê=Darwin and Hegel, with other Philosophical Studies|cih=London|weşanxane=S. Sonnenschein & Co.; New York, N.Y.: [[w:Macmillan Publishers (United States)|Macmillan & Co.]]|sal=1893|rûpel=72|rêz-sade=note 1|urlya-rûpelê=http://books.google.com/books?id=8yGEAHvAwiIC&q=%22An+absolute+system+cannot+afford+to+leave+any+nook+or+cranny+of+existence+unexplored%22#search_anchor|oclc=3299658|jêgirtin=An absolute system cannot afford to leave '''any nook or cranny''' of existence unexplored.}}
#* {{jêder-kovar/ceribandin|en
|nivîskar=w:Hayden Carruth|sernav=Making It New|kovar=w:The Hudson Review|cih=New York, N.Y.|weşanxane=Hudson Review, Inc.|meh=Summer|sal=1968|cild=XXI|hejmar=2|issn=2325-5935|oclc=920393805|versiyona-nû=reprinted as|cure2=book|beş2=From ‘Making It New’ [''Body Rags'']|edîtor2=Howard Nelson|sernav2=On the Poetry of Galway Kinnell: The Wages of Dying|serî2=Under Discussion|cih2=Ann Arbor, Mich.|weşanxane2={{w|University of Michigan Press}}|sal2=1987|rûpel2=75|urlya-rûpelê2=https://books.google.com/books?id=7cXV26PmJUYC&pg=PA75|isbn2=978-0-472-09376-2|jêgirtin=In his [{{w|Galway Kinnell}}'s] new book, ''Body Rags'', he has brought this style to a kind of perfection, especially in two poems about the killing of animals, "The Porcupine" and "The Bear." These are the '''grisliest''' poems I have ever read.}}
#* {{jêder-kitêb/ceribandin|fr
|sal=1973|nivîskar=w:Claude Simon|sernav=Tryptique|weşanxane=Éditions de Minuit|rûpel=12|jêgirtin=Les sons de la cloche '''égrenant''' les quarts, les demies et les heures {{...}}|t=The sounds of the clock '''rattling off''' the quarters, the halves and the hours {{...}}}}
#* {{jêder-kitêb/ceribandin|de
|sal=1999|nivîskar=Paul Hoffmann|hevnivîskar=Stefan H. Brandenburger; Ulrike Brauner; Thomas Hieke|sernav=Q 22:28, 30: You Will Judge the Twelve Tribes of Israel|serî=Documenta Q: Reconstructions of Q Through Two Centuries of Gospel Research Excerpted, Sorted and Evaluated|cildê-seriyê=volume 5|isbn=90-429-0734-7|rûpel=189|jêgirtin=Zumal in beiden Fällen eine Partizipialform vorliegt, liegt es nahe, die '''Zeitworte''' als Varianten anzusehen.}}
#* {{jêder-kitêb/ceribandin|ru
|nivîskar=lw:ru:[[Филарет (Дроздов)|Митрополи́т Филаре́т]]<tr:-><t:Mitropolit Filaret>|beş=ru:Сло́во предъ приведеніемъ къ присягѣ Санктпетербурксаго дворя́нства и гражданъ для избранія суде́й|tarîx=1818-02-01<!--Converted to Gregorian-->|sala-weşandinê=1873|cih=Moscow|sernav=ru:Сочинеиія Филарета Митрополита Московскаго и Коломенскаго|cild=I|rûpel=253|urlya-rûpelê=https://books.google.com/books?id=yWEcAAAAMAAJ&pg=PA253|jêgirtin=[[изъ|Изъ]] [[сей|сего́]] [[ви́дно]], [[что]] [[судья|судія́]], [[как|какъ]] [[блюсти́тель]] [[общественный|обще́ственнаго]] [[порядок|поря́дка]], [[должен|долженъ]] '''объима́ть''' [[и]] [[проника́ть]] [[мысль|мы́слію]] [[весь]] [[состав|соста́въ]] [[общество|о́бщества]];|t=From this it becomes discernible that a judge, in his function as a warden of public order, should '''enclose''' and traverse the whole body of the society;}}
#* {{jêder-kitêb/ceribandin|yi
|nivîskar=I.J. Singer|sernav=Fun a velt vos iz nishto mer|sal=1946|beş=yi:אַ ייד אַ מלמד פֿאַרגלוסט זיך צו ווערן אַ מלאך אום פּורים און פֿאַרלירט, נעבעך, אַן אויג<subst:ייד/ייִד,מלמד/מעלאַמעד,פּורים/פּורעם,מלאך/מאַלעך><t:A Jew, a teacher, decides to become an angel on Purim and, unfortunately, loses an eye>|jêgirtin=יעדן '''זמן''' אויפֿסניי, חול־המועד סוכות אָדער חול־המועד פּסח, בין איך געווען זייער האָפֿערדיק, וואָס מען האָט מיך גענומען איין מלמד און אָנגעדונגען צו אַן אַנדערן, גלײבנדיק, אַז דער נייער וועט זיין בעסער ווי דער אַלטער.|subst=זמן//זמאַן,חול־המועד//כאַלעמויעד,סוכות//סוקוס,פּסח//פּייסעך,מלמד//מעלאַמעד,נייער//נײַער|tercime=Every '''term''' anew, during the intermediary periods of Sukkot and Pesach, I was very confident in being taken from one melamed and given to another, believing the new one would be better than the old.}}
#* {{jêder-kitêb/ceribandin|ru
|sal=1983|nivîskar=w:Samuil Marshak|sernav=Баллада о королевском бутерброде<subst:бутер/бутэр><t:Ballad of the King's Bread>|orîjînal=The King's Breakfast|ji-aliyê=w:A. A. Milne|metna-orîjînal=en:"Nobody," / He whimpered, / "Could call me / A fussy man; / I only want / A little bit / Of butter for / My bread!"|zimanê-orîjînal=en|jêgirtin=— [[никто|Никто́]] [[не]] [[сказать|ска́жет]], [[бу́дто]] [[я]] / [[тиран|Тира́н]] [[и]] [[сумасбро́д]], / [[за|За]] [[то́]], [[что]] [[к]] [[чай|ча́ю]] [[я]] [[любить|люблю́]] / [[хороший|Хоро́ший]] '''бутербро́д'''.|subst=бутербро́д/бутэрбро́д|t="No one would say that I / Am a tyrant and madman, / For the fact that with my tea I like / A good '''bread-and-butter sandwich'''."}}
#* {{jêder-kitêb/ceribandin|ota
|sal=1922|sernav=ota:ادارهٔ حرب و سیاست<tr:Idare-i harb ve siyaset>|cih=Istanbul|weşanxane=ota:مطبعهٔ عثمانیه|rûpel=176|urlya-rûpelê=https://babel.hathitrust.org/cgi/pt?id=uc1.aa0006749931&view=1up&seq=182|versiyona-nû=translation of|ziman2=de|sal2=1922|2nivîskar=w:Erich Ludendorff|sernav2=Kriegsführung und Politik|cih2=Berlin|weşanxane2=E. S. Mittler & Sohn|rûpel2=199|urlya-rûpelê2=https://archive.org/details/kriegfhrungund00lude/page/199|jêgirtin=شرقی غالیچیا، و بوقوونیاده شمندوفر نقاباتی تأسس ایتدكدن صكره شرقده '''صلحی''' تأمین ایچون دینیهستر نهری جنوبندن روس — رومن جبههسنه قارشو حربه دوام ایتمك فكرندن فراغت ایتمك لازمكلیورایدی. بر قصقاچ تأثیری یاپهبیلمك ایچون بری قلاص غربندن یوقارهسهرهت اوزرینه و دیكری دینیهستر جنوبندن اولمق اوزره مضاعف بر تعرض اجراسی لازمكلیور ایده.|tr=Şarki Galiçya, ve Bukovinyada şömendüfer nakabatı tesis itdikden soñra şarkda sulhı temin içün Dinyester nehri cenubundan rus-rumen cebhesine karşu harbe devam itmek fikrinden feragat itmek lazım geliyor idi. Bir kıskaç tesiri yapabilmek içün biri Kalas garbından yukara-yı Seret üzerine ve diğeri Dinyester cenubundan olmak üzere muzaaf bir taarruz icrası lazım geliyor idi.|metna-orîjînal=de:Unsere weitere Absicht, nach Herstellung der Eisenbahnverbindungen in Ostgalizien und in der Bukowina zur Erzwingung des Friedens im Osten den Feldzug gegen die russisch-rumänische Front südlich des Dnjestr fortzusetzen, mußte aufgegeben werden. Er sollte in einem doppelten Angriff über den oberen Sereth westlich Galatz und hart südlich des Dnjestr bestehen und auch zangenartig wirken.|t=Our further intention, the military campaign against the Russian-Romanian front to be continued south of the Dniestr to force '''peace''' in the east, after procurement of the railroad connections in Eastern Galicia and in the Bukovina, had to be abandoned. It was supposed to consist in a double attack over the upper Siret west of Galați as well as hard south of the Dniestr and go like pincers.}}
#* {{jêder-kitêb/ceribandin|it
|sernav=L'œuvre complète de Tchouang-tseu|terciman=Liou Kia-hway<f:Hant:劉家槐><f:Pinyin:Liú Jiāhuái>|orîjînal={{w|Zhuangzi (book)|Zhuang-zi}}|ji-aliyê=莊周<t:Zhuang Zhou>|zimanê-orîjînal=och|sala-orîjînal=c. 300 {{BCE}}|sal=1969|sal2=1982|sala-weşandinê2=2010|versiyona-nû=translated as|terciman2=Carlo Laurenti; Christine Leverd|sernav2=Zhuang-zi [Chuang-tzu]|serî2=Gli Adelphi|cildê-seriyê2=41|beş2=Libertà naturale|tercimeya-beşê2=Natural Freedom|çap2=6th|cih2=Milan|weşanxane2={{w|Adelphi Edizioni}}|rûpel2=17|isbn2=978-88-459-0950-4|metna-orîjînal=och:之人也,物莫之傷,大浸稽天而不溺,大旱、金石流、土山焦而不熱。|jêgirtin=Se le acque si alzassero fino al cielo, non annegherebbero. Se la siccità '''liquefacesse''' i metalli e infiammasse le montagne, non ne sarebbero neppure sfiorati.|t=If the waters were to rise to the skies, they wouldn't drown. If drought were to '''liquefy''' metals, and set fire to the mountains, they wouldn't even be touched by it.}}
#* {{jêder-kitêb/ceribandin|fr
|zimanê-termê=en|sal=1677|nivîskar=Guy Miege [''i.e.'', [[w:fr:Guy Miège|Guy Miège]]]|made=HAGGESS|urlya-madeyê=https://books.google.com/books?id=YbWtTwVbLioC&pg=PP857|sernav=A New Dictionary French and English, with Another English and French; According to the Present Use, and Modern Orthography of the French. Inrich’d with New Words, Choice Phrases, and Apposite Proverbs; Digested into a Most Accurate Method; and Contrived for the Use Both of English and Foreiners|cih=London|weşanxane=Printed by Tho[mas] Dawks, for Thomas Basset, at the George, near [[w:Clifford's Inn|Cliffords-inn]], in [[w:Fleet Street|Fleetstreet]]|stûn=3|oclc=874955677|jêgirtin='''HAGGESS''', '''haggas''', or '''haggis''', ſort d'appret fait avec des herbes, du larde caupé menu, des épices, des œufs, & du fromage, le tout mis enſemble & bouilli dans une panſe de brebis.|t='''HAGGESS''', '''haggas''', or '''haggis''', a sort of preparation made with herbs, bacon, spices, eggs, cheese, all put together and boiled in a sheep's belly.|brackets=1}}
#*{{jêder-kovar/ceribandin|en|sernav=Art VIII.—Who are the True Conservatives?|kovar=The Quarterly Christian Spectator|cih=New Haven, Conn.|weşanxane=Published for the proprietor by Hezekiah Howe [...]|meh=November|sal=1838|cild=X|hejmar=IV|rûpel=608|urlya-rûpelê=https://books.google.com/books?id=S7MAAAAAMAAJ&pg=PA608|oclc=5090641|jêgirtin=They have guarded a few minds against '''sciolism''' and pretension in philosophy, and they have given a new and more imposing air to '''sciolism''' itself, and placed in the hands of the charlatan enchantments and magical arts, which hold fast its victims with a potency never granted to an inferior spell. There is no '''sciolism''' now which is more dangerous than that which is so very careful to inform us of its entire freedom from all that is superficial, {{...}}}}
#*{{jêder-kovar/ceribandin|en
|tarîx=March 8, 2023|paşnav=Wu|pêşnav=Huizhong|nivîskar2=Johnson Lai|sernav=Taiwan suspects Chinese ships cut islands’ internet cables|kovar={{w|The Washington Post}}|urlya-arşîvê=https://web.archive.org/web/20230311220100/https://www.washingtonpost.com/business/2023/03/08/matsu-taiwan-internet-cables-cut-china/d322fcae-bd70-11ed-9350-7c5fccd598ad_story.html|tarîxa-arşîvê=11 March 2023|issn=0190-8286|oclc=2269358|qisim=Business|tarîx2=April 18, 2023|sernav2=Chinese ships cut internet of Taiwan’s outlying islands|cure2=journal|kovar2={{w|AP News}}|urlya-arşîvê2=https://web.archive.org/web/20230308053048/https://apnews.com/article/matsu-taiwan-internet-cables-cut-china-65f10f5f73a346fa788436366d7a7c70|jêgirtin=Wang Chung Ming, the head of '''Lienchiang''' County, as the Matsu islands are officially called, said he and the legislator from Matsu went to Taipei shortly after the internet broke down to ask for help, and was told they would get priority in any future internet backup plans.{{...}}<br>Wang, the head of '''Lienchiang''' County, said he had mentioned the cables on a recent visit to China, where he had met an executive from China Mobile.}}
#*{{jêder-kovar/ceribandin|en
|sal=1994|meh=April|kovar=光華|tercimeya-kovar=Sinorama|url=https://books.google.com/books?id=GIRDAAAAYAAJ|cild=19|hejmar=1-6|weşanxane={{w|Government Information Office}}|issn=0256-9043|oclc=20549278|sal2=1994|meh2=April|2nivîskar=Yuan-ching Cheng|cure2=journal|sernav2=Kinmen- -From Battlefield to Tourist Destination|kovar2=台灣光華雜誌<t:Taiwan Panorama>|terciman2=Robert Taylor|urlya-arşîvê2=https://web.archive.org/web/20221021103917/https://www.taiwan-panorama.com/en/Articles/Details?Guid=c9335c12-7ec3-40e7-96be-33a81416a223|tarîxa-arşîvê2=21 October 2022|qisim2=Around Taiwan|jêgirtin=The temple to Colonel Li Kuang-chien lies near Hsiputou village in the northwest of Kinmen Island, around five kilometers from the town of '''Chincheng''' and next to the famous Hill 132.{{...}}<br>Because Kinmen was settled early, it bears many traces of the past. There are 21 listed historical monuments scattered all over the island. The only Grade I monument among them is the "Chiehhsiao Arch," commemorating Chiu Liang-kung's mother, located in East Houpu Street in the center of '''Chincheng'''.}}
== {{ziman|ku}} ==
=== Lêker ===
[[Wêne:Example.jpg|thumb|Mînak]]
{{lêker|ku}}
# [[testsdfsdafdasfas]]
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Mîr Kamûran di sala 1947 an de li '''Fransayê''' bicîh bû, û heta 1970ê li Xwendegeha zimanên rojhilatî ya Zaningeha Parîsê Profesorîya ziman û çanda Kurdî kir.|nivîskar=Kamuran A.Bedir Khan|nivîskar2=Josefe Bertolino|nivîskar3=Kendal Nezan|sernav=Institut Kurde De Paris Ferhenga Kurdî-Fransizî|cih=Istanbul|weşanxane={{w|Weşanên Avesta|Avesta}}|sal=2017|çap=1|rûpel=XXXIX|isbn=978-2-360134311}}
#* {{jêder-malper/ceribandin|ku|jêgirtin=Li gor amareke ajanseke selametiya xorakê, xelkê '''Fransa''' salane nêzîkî 4,000 ton Beqan dixwin, ku ew jî bi qasî goştê 40 nehengên şîn e. Ji bo dabînkirina pêwîstiya bazarê jî, li '''Fransayê''' kêlgeh û cihên taybet ên xwedîkirina Beqan hene. Beşek ji wêneyên di vê wênenûçeyê de, kêlgeha xwedîkirina Beqan li navçeyeke '''Fransayê''' ye, herwiha beşek ji wêneyan jî awayê patin û çêkirina xwarinê ji goştê Beqan di xwaringehan de û pêşkêşkirina wan bi mêvanan nîşan didin.|nivîskar=ciwan ikbal|sernav=Fransî di salekê de 4,000 ton Beqan dixwin|url=https://www.rudaw.net/kurmanci/world/101020201|dîrok=10.10.2020}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Bale awayeke dansê ye. Babetek ji şanoya musîkal e. Ji bo dansa baleyê, divê meriv cansivik û nerm be. Loma jî bale gelekî antremanê dixwaze û divê meriv di temenekî biçûk de dest pê bike. Di baleyê de, meriv li gor livên diyarkirî dansê dike. Hinek ji wan tu di wêne de dibînî. Ji ber ku bale li '''Fransayê''' bi pêş ketiye, loma navên livên wê jî bi [[fransî]] ne.|amadekar=Komxebat|sernav=Ansîklopediya Zarokan|cih=Denmark<br>|weşanxane={{w|Weşanên APECl|APEC}}|sal=2004|rûpel=25|isbn=91-89675-32-0}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Hekayêta Betal Kirina '''Xezeta''' Hetawî Kurd|sal=~1910|sala-weşandinê=2026|nivîskar=Mela Seîdê Şemdînanî|amadekar=David N. MacKenzie, Ergin Öpengin|url=http://books.google.com.tr/books?id=eqvrEQAAQBAJ&pg=PA383&dq=%22wan+xezetan%22+OR+%22xezet%C3%AA%22+OR+%22xezetin%22+OR+%22xezetek%C3%AA%22+OR+%22xezeta%22+OR+%22w%C3%AA+xezet%C3%AA%22+OR+%22xezetino%22+OR+%22xezetine%22&hl=&cd=2&source=gbs_api|sernav=Shamdinani Stories I: Kurdish Writings on the History and Culture of Central Kurdistan by Mullah Saʼid Shamdinani|weşanxane=BRILL|ISBN=9789004754430|rûpel=383}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Baş e, '''xezete''' dê bi çi ezmanî derkevit?|nivîskar=E. Rehmî Ji Mala Hekarîyan|amadekar=M. E. Bozarslan|sernav=Jîn: kovara Kurdî-Tirkî. Cild 1|weşanxane=Weşanxana Deng|cih=Uppsala|sala-weşandinê=1985|sal=1918|rûpel=310|isbn=978-91-970702-0-1}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=ligor{{sic}} danezanên '''xezete''' ên Turk hezar insan li kurdistan hatin kuştin ku ji % 70 jin û zaruk kal » pîrbûn|nivîskar=Hesen Hişyar Serdî|sernav=Dîtin û Bîrhatinên Min|weşanxane=Çapxana Emîral|cih=Beyrût|sal=1991|sala-weşandinê=2000|rûpel=380|url=https://bnk.institutkurde.org/images/pdf/CTNPMX4FR6.pdf}}
#* {{jêder-malper/ceribandin|ku|jêgirtin='''Xezeteyên''' Kurdî yên li Stenbolê derdiketin bên îstisnakirin, xezeteyekî millî û wetenî yê ku yekemîn car li Kurdistanê hatîye neşirkirin, ew jî xezeteya Zarê Kirmancî ye, ku min xwendîye.|nivîskar=Seîd Veroj|sernav=Mesûd Fanî (Dr. Mesûd Fanî Bîlgîlî)|websîte=Rûpela nû|url=https://www.rupelanu.com/seid-veroj-mesud-fani-dr-mesud-fani-bilgili-17258h.htm|tarîx=2021-05-04}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=me '''xezete''' da destê wî jî.|sal=2022|nivîskar=Abdurrahman Benek|url=http://books.google.com.tr/books?id=SdSUEAAAQBAJ&pg=PA33&dq=%22wan+xezetan%22+OR+%22xezet%C3%AA%22+OR+%22xezetin%22+OR+%22xezetek%C3%AA%22+OR+%22xezeta%22+OR+%22w%C3%AA+xezet%C3%AA%22+OR+%22xezetino%22+OR+%22xezetine%22&hl=&cd=3&source=gbs_api|sernav=Payiza Mezinan|weşanxane=Nûbihar|ISBN=9786059413749|rûpel=33}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Jêgirtinek ji kitêbekê.|nivîskar=Rojen Barnas|sernav=Sîlvan/ Silîvan / FARQÎN|sal=2013|weşanxane=Nûbihar|rûpel=189|isbn= 9786055402884, 6055402882}}
#* {{jêder-malper/ceribandin|ku|nivîskar=Gazete Duvar|sernav=Şairê dubeytan: Baba Tahirê Uryan|websîte=https://www.gazeteduvar.com.tr|url=https://www.gazeteduvar.com.tr/saire-dubeytan-baba-tahire-uryan-haber-1753893|tarîx=2025-02-02|jêgirtin=adfasdf}}
#* {{jêder-kitêb/ceribandin|ku|sernav=Shamdinani Stories I|weşanxane=Brill|sal=2026|isbn=978-90-04-75443-0|url=https://brill.com/edcollbook-oa/title/74006|jêgirtin=test}}
# Maneya 3an.
#* {{quote-web|ckb|author=author|last=last|first=first|authorlink=authorlink|author2=author2|last2=last2|first2=first2|authorlink2=authorlink2|author3=author3|last3=last3|first3=first3|authorlink3=authorlink3|author4=author4|last4=last4|first4=first4|authorlink4=authorlink4|author5=author5|last5=last5|first5=first5|authorlink5=authorlink5|coauthors=coauthors|quotee=quotee|translator=translator|translators=translators|title=title|trans-title=trans-title|work=work|trans-work=trans-work|url=url|archiveurl=archiveurl|archivedate=archivedate|format=format|worklang=worklang|location=location|publisher=publisher|month=month|year=year|year_published=year_published|date=date|accessdate=accessdate|nodate=nodate|page=page|pages=pages|pageurl=pageurl|section=section|sectionurl=sectionurl|text=text|passage=passage|lang=lang|brackets=brackets|t=t|translation=translation|lit=lit|tr=tr|transliteration=transliteration|subst=subst}}
#* {{quote-book|1=1|author=author|authorlink=authorlink|chapter=chapter|editor=editor|editors=editors|title=title|url=url|archiveurl=archiveurl|archivedate=archivedate|edition=edition|location=location|publisher=publisher|date=date|isbn=isbn|oclc=oclc|volume=volume|section=section|sectionurl=sectionurl|page=page|pages=pages|pageurl=pageurl|text=text|t=t|tr=}}
#* {{quote-journal|1=1|year=year|author=author|title=title|journal=journal|url=url|volume=volume|issue=issue|page=page|text=text|t=}}
auwzzn8fpe8uk67r4zi0awdlj5va08b
6291567
6291565
2026-07-26T14:59:23Z
Wikihez
7344
kurdîkirina şablonên jêderê (citeKurdifier)
6291567
wikitext
text/x-wiki
{{/vê rêzê neguhêre}}
== {{ziman|en}} ==
=== Lêker ===
[[Wêne:Example.jpg|thumb|Mînak]]
{{lêker|en}}
# Maneya 1ê.
#: {{mk|ku|'''Nimûneyeke''' bikaranînê ya vê peyvê.}}
#* {{jêder-kovar/ceribandin|en|nivîskar=Justin Chang|lînka-nivîskar=Justin Chang|sernav=The Day He Arrives [film review]|url=https://variety.com/2011/film/markets-festivals/the-day-he-arrives-1117945326/|urlya-arşîvê=https://web.archive.org/web/20170628015008/https://variety.com/2011/film/markets-festivals/the-day-he-arrives-1117945326/|tarîxa-arşîvê=28 June 2017|kovar=[[w:Variety (magazine)|Variety]]|tarîx=25 May 2011|issn=0042-2738|oclc=960911146|jêgirtin=The outline could apply to most of the self-effacing, increasingly inward-looking comedies written and directed by [[w:Hong Sang-soo|Hong Sang-soo]], and it certainly describes "[[w:The Day He Arrives|The Day He Arrives]]," an agreeably meandering exercise that brings some clever French New Wave '''fillips''' and structural repetitions to Hong's characteristically boozy party.}}
#* {{jêder-kitêb/ceribandin|en|nivîskar=F[rancis] Scott Fitzgerald|lînka-nivîskar=F. Scott Fitzgerald|beş=IX|sernav=[[w:The Great Gatsby|The Great Gatsby]]|cih=New York, N.Y.|weşanxane=[[w:Charles Scribner's Sons|Charles Scribner’s Sons]]|sal=1925|oclc=884653065|cih2=New York, N.Y.|weşanxane2=Charles Scribner’s Sons|sal2=1953|isbn2=978-0-684-16498-4|rûpel2=182|urlya-rûpelê2=https://archive.org/stream/in.ernet.dli.2015.184960/2015.184960.The-Great-Gatsby#page/n185/mode/1up|jêgirtin=So we beat on, boats against the current, borne back '''ceaselessly''' into the past.}}
#* {{jêder-malper/ceribandin|en|nivîskar=Meg Downey|sernav=Marvel’s Comic Soul Stone could Explain the Jump from Infinity War’s Ending to Avengers 4: A Brief History of Crazy Multidimensional Power|url=https://www.polygon.com/2018/5/7/17328230/infinity-war-avengers-4-marvel-comics-soul-stone|urlya-arşîvê=https://web.archive.org/web/20180624110542/https://www.polygon.com/2018/5/7/17328230/infinity-war-avengers-4-marvel-comics-soul-stone|tarîxa-arşîvê=24 June 2018|xebat=[[w:Polygon (website)|Polygon]]|tarîx=7 May 2018|jêgirtin=There’s a very real chance that, rather than crumbling into the dust and floating off into the '''ether''', Thanos’s victims [in the film ''[[w:Avengers: Infinity War|Avengers: Infinity War]]''] were actually sucked up into the Soul Stone.}}
#* {{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=The Republic of Molossia has played a considerable role in developing '''intermicronational''' diplomacy.}}
#* {{jêder-kitêb/ceribandin|en
|sal=c. 1988|nivîskar=Peter W. Rodino, Jr.; et al.|sernav=Report of the Congressional Committees Investigating the Iran/Contra Affair|urlya-rûpelê=http://books.google.com/books?id=ew_K3auTwEgC&pg=PA646&dq=phone+call|rûpel=646|jêgirtin=On November 20, they had a '''phone call''' and later a meeting with others lasting at least 1 1/2 hours.}}
#* {{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=Centrally acting '''sympathicolytics''' cause a decrease in the sympathetic outflow from the brainstem. This reduction in sympathetic outflow brings about vasodilatation of the peripheral vascular bed with a concomitant drop in the blood pressure}}
#* {{jêder-kitêb/ceribandin|en
|sal=2005|edîtor=Martin Endress; George Psathas; Hisashi Nasu|beş=Between the Everyday Life-World and the World of Social Scientific Theory—Towards an “Adequate” Social Thory|nivîskar=Hisashi Nasu|sernav=Explorations of the Life-World: Continuing Dialogues with Alfred Schutz|weşanxane=Springer|isbn=1402032196|rûpel=136|jêgirtin=It is not only the '''man on the street''' who is characterized by taking one's own system of relevance for granted. This holds true for the expert,....}}
#* {{jêder-kitêb/ceribandin|en
|sal=2002|nivîskar=Peter Shirlow|beş=Sinn Féin: Beyond; within Containment|hejmara-beşê=4|edîtor=Stefan Wolff; Jörg Neuheiser|sernav=Peace at Last?: The Impact of the Good Friday Agreement on Northern Ireland|weşanxane=Berghahn Books|isbn=157181518X|urlya-rûpelê=http://books.google.com/books?id=QyInASKCFjcC&pg=PA62&dq=ethnosectarian&sig=Q54T1Vz3vsKnqd05666N1ddFki8|rûpel=62|jêgirtin=Moreover, language, discourse, and dialogue, their definition and mobilisation, create chains of equivalence which enclose subjects within not only '''ethnosectarian''' but at times entirely self-referential and self-sustained notions of identity, practice and collective devotion.}}
#* {{jêder-kitêb/ceribandin|en
|sernav=Extreme Hydrological Events: New Concepts for Security|edîtor=O.F. Vasiliev; P.H.A.J.M. van Gelder; E.J. Plate; M.V. Bolgov|sal=2007|weşanxane=[[w:Springer Science+Business Media|Springer]]|isbn=1402057393|beş=Predetermination of Floods|nivîskar=Pierre Hubert; Ioulia Tchiguirinskaia; Daniel Schertzer; Hocine Bendjoudi; Shaun Lovejoy|qisim=On Probability and Return Periods of Floods|hejmara-qismê=2|rûpel=187|jêgirtin=It is perfectly legitimate to be interested, particularly for safety studies, by millennium floods or floods or '''decamillennium''' floods (i.e. having a probability 0,001 and 0,0001 respectively to occur in the course of given year) of a river which did not exist five thousand years ago and that will not exist perhaps any more in ten thousand years, similarly to the industrial interest in the probability of faults which will not undoubtedly occur, because the minimization of their probability is an aspect of a policy of industrial security.}}
#* {{jêder-kitêb/ceribandin|en
|sal=1995|nivîskar=w:Robert M. Solovay|nivîskarê-sereke=w:Kurt Gödel|edîtor=w:Solomon Feferman; w:John W. Dawson Jr.; w:Warren Goldfarb; {{w|Charles Parsons (philosopher)|Charles Parsons}}; w:Robert M. Solovay|beş=Notes to *1970 [{{w|Godel's ontological proof}}]|sernav=Kurt Gödel: Collected Works: Volume III|urlya-rûpelê=https://books.google.com.au/books?id=gDzbuUwma5MC&pg=PA419&dq=%22Hausdorff+gap%22%7C%22Hausdorff+gaps%22&hl=en&newbks=1&newbks_redir=0&sa=X&ved=2ahUKEwjo-o7D9OT7AhVQJUQIHaSlBIgQ6AF6BAhXEAI#v=onepage&q=%22Hausdorff%20gap%22%7C%22Hausdorff%20gaps%22&f=false|rûpel=419|weşanxane=w:Oxford University Press|jêgirtin=Of course, Hausdorff did not talk of models of set theory or prove absoluteness results. He gave a direct construction in ''ZFC'' of what is now called a '''Hausdorff gap''' and proved properties 1 through 4 for his construction. The notion of a '''Hausdorff gap''' and the proof that '''Hausdorff gaps''' are indestructible under cardinal preserving extensions (property 5 above) are due to Kunen (unpublished).}}
#* {{jêder-kitêb/ceribandin|en
|sal=1925|nivîskar={{w|F. Scott Fitzgerald|F[rancis] Scott Fitzgerald}}|beş=IX|sernav=w:The Great Gatsby|cih=New York, N.Y.|weşanxane=w:Charles Scribner's Sons|oclc=884653065|sal2=1953|cih2=New York, N.Y.|weşanxane2=Charles Scribner’s Sons|isbn2=978-0-684-16498-4|rûpel2=182|urlya-rûpelê2=https://archive.org/stream/in.ernet.dli.2015.184960/2015.184960.The-Great-Gatsby#page/n185/mode/1up|jêgirtin=So we beat on, boats against the current, borne back '''ceaselessly''' into the past.|lccn=gm 95-1}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar=w:John Boyle O'Reilly|beş=On the Trail|sernav=[[w:Moondyne|Moondyne: A Story from the Under-world]]|cih=London|weşanxane=[[w:Routledge|George Routledge and Sons]]|sal=1878|sala-weşandinê=1879|oclc=39983928|sernav2=Moondyne: A Story of Convict Life in Australia|cih2=London|weşanxane2=George Routledge & Sons, Limited, Broadway House, {{w|Ludgate Hill}}|sal2=[1880s]|qisim2=book first|pages2=23–24|urlya-rûpelê2=https://archive.org/stream/moondynestory00oreirich#page/24/mode/1up/|oclc2=83033698|jêgirtin=It was sore travelling for horse and man under the blazing sun, with no food nor water save what he pressed from the pith of the palms, and even these were growing scarce. The only life on the plains was the hard and dusty scrub. Every hour brought a more hopeless and '''grislier''' desolation.}}
#* {{jêder-kitêb/ceribandin|en
|sal=1966|sala-orîjînal=1858|nivîskar=w:Hans Christian Andersen|terciman=Pat Shaw Iversen|orîjînal=Suppe paa en Pølsepind|zimanê-orîjînal=da|beş=Soup from a Sausage Peg|sernav=The Snow Queen and Other Tales|serî=Signet Classic|cildê-seriyê=CT334|cih=New York, N.Y.|weşanxane=w:New American Library|rûpel=224|urlya-rûpelê=http://books.google.com/books?id=2tjWAAAAMAAJ&q=%22from+your+own+nook+and+cranny%22#search_anchor|oclc=636818779|metna-orîjînal=da:Det er underligt at komme hjemme fra sin egen '''Krinkelkrog''', gaae med Skib, der ogsaa er en Slags '''Krinkelkrog''', og saa pludselig være over hundrede Mile borte og staae i et fremmedt Land.|jêgirtin=It’s strange to come away from home, from your own '''nook and cranny''', to go by ship – which is also a kind of '''nook and cranny''' – and then suddenly be more than a hundred miles away and stand in a foreign land!}}
#* {{jêder-kitêb/ceribandin|en
|sal=2013|nivîskar=Bernd Klauer; Reiner Manstetten; Thomas Petersen; Johannes Schiller|zimanê-eserê=de|beş=Materielle Bestände|sernav=Die Kunst, langfristig zu denken : Wege zur Nachhaltigkeit<t:The art of thinking long-term : paths to sustainability>|cih=Baden-Baden|weşanxane=Nomos|versiyona-nû=translated as|terciman2=Kathleen Cross|sal2=2016|sernav2=Sustainability and the Art of Long-Term Thinking|beş2=Material stocks|cih2=London; New York|weşanxane2=Routledge, Taylor & Francis Group|qisim2=4.3.1. Material sets and stocks|rûpel2=44|isbn2=9781134986187|jêgirtin=The property of '''appertainment''' of a set is therefore the property which, by definition, has to be given in every element of the set.}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar={{w|Andrew Seth Pringle-Pattison|Andrew Seth}}|sernav=Hegelianism and Personality|çap=2nd|sal=1887|sala-weşandinê=1893|rûpel=216|rêz-sade=footnote|cih=Edinburgh; London|weşanxane=William Blackwood and Sons|versiyona-nû=quoted in|sal2=1893|2nivîskar=w:David George Ritchie|sernav2=Darwin and Hegel, with other Philosophical Studies|cih2=London|weşanxane2=S. Sonnenschein & Co.; New York, N.Y.: [[w:Macmillan Publishers (United States)|Macmillan & Co.]]|rûpel2=72|urlya-rûpelê2=http://books.google.com/books?id=8yGEAHvAwiIC&q=%22An+absolute+system+cannot+afford+to+leave+any+nook+or+cranny+of+existence+unexplored%22#search_anchor|rêz-sade2=note 1|oclc2=3299658|jêgirtin=An absolute system cannot afford to leave '''any nook or cranny''' of existence unexplored.}}
#* {{jêder-kitêb/ceribandin|en
|nivîskar=w:David George Ritchie|jê-tê-girtin=w:Andrew Seth Pringle-Pattison|sernav=Hegelianism and Personality|çap=2nd|kesên-din=1893, page 216, footnote|cihê-jêgirtinê=Darwin and Hegel, with other Philosophical Studies|cih=London|weşanxane=S. Sonnenschein & Co.; New York, N.Y.: [[w:Macmillan Publishers (United States)|Macmillan & Co.]]|sal=1893|rûpel=72|rêz-sade=note 1|urlya-rûpelê=http://books.google.com/books?id=8yGEAHvAwiIC&q=%22An+absolute+system+cannot+afford+to+leave+any+nook+or+cranny+of+existence+unexplored%22#search_anchor|oclc=3299658|jêgirtin=An absolute system cannot afford to leave '''any nook or cranny''' of existence unexplored.}}
#* {{jêder-kovar/ceribandin|en
|nivîskar=w:Hayden Carruth|sernav=Making It New|kovar=w:The Hudson Review|cih=New York, N.Y.|weşanxane=Hudson Review, Inc.|meh=Summer|sal=1968|cild=XXI|hejmar=2|issn=2325-5935|oclc=920393805|versiyona-nû=reprinted as|cure2=book|beş2=From ‘Making It New’ [''Body Rags'']|edîtor2=Howard Nelson|sernav2=On the Poetry of Galway Kinnell: The Wages of Dying|serî2=Under Discussion|cih2=Ann Arbor, Mich.|weşanxane2={{w|University of Michigan Press}}|sal2=1987|rûpel2=75|urlya-rûpelê2=https://books.google.com/books?id=7cXV26PmJUYC&pg=PA75|isbn2=978-0-472-09376-2|jêgirtin=In his [{{w|Galway Kinnell}}'s] new book, ''Body Rags'', he has brought this style to a kind of perfection, especially in two poems about the killing of animals, "The Porcupine" and "The Bear." These are the '''grisliest''' poems I have ever read.}}
#* {{jêder-kitêb/ceribandin|fr
|sal=1973|nivîskar=w:Claude Simon|sernav=Tryptique|weşanxane=Éditions de Minuit|rûpel=12|jêgirtin=Les sons de la cloche '''égrenant''' les quarts, les demies et les heures {{...}}|t=The sounds of the clock '''rattling off''' the quarters, the halves and the hours {{...}}}}
#* {{jêder-kitêb/ceribandin|de
|sal=1999|nivîskar=Paul Hoffmann|hevnivîskar=Stefan H. Brandenburger; Ulrike Brauner; Thomas Hieke|sernav=Q 22:28, 30: You Will Judge the Twelve Tribes of Israel|serî=Documenta Q: Reconstructions of Q Through Two Centuries of Gospel Research Excerpted, Sorted and Evaluated|cildê-seriyê=volume 5|isbn=90-429-0734-7|rûpel=189|jêgirtin=Zumal in beiden Fällen eine Partizipialform vorliegt, liegt es nahe, die '''Zeitworte''' als Varianten anzusehen.}}
#* {{jêder-kitêb/ceribandin|ru
|nivîskar=lw:ru:[[Филарет (Дроздов)|Митрополи́т Филаре́т]]<tr:-><t:Mitropolit Filaret>|beş=ru:Сло́во предъ приведеніемъ къ присягѣ Санктпетербурксаго дворя́нства и гражданъ для избранія суде́й|tarîx=1818-02-01<!--Converted to Gregorian-->|sala-weşandinê=1873|cih=Moscow|sernav=ru:Сочинеиія Филарета Митрополита Московскаго и Коломенскаго|cild=I|rûpel=253|urlya-rûpelê=https://books.google.com/books?id=yWEcAAAAMAAJ&pg=PA253|jêgirtin=[[изъ|Изъ]] [[сей|сего́]] [[ви́дно]], [[что]] [[судья|судія́]], [[как|какъ]] [[блюсти́тель]] [[общественный|обще́ственнаго]] [[порядок|поря́дка]], [[должен|долженъ]] '''объима́ть''' [[и]] [[проника́ть]] [[мысль|мы́слію]] [[весь]] [[состав|соста́въ]] [[общество|о́бщества]];|t=From this it becomes discernible that a judge, in his function as a warden of public order, should '''enclose''' and traverse the whole body of the society;}}
#* {{jêder-kitêb/ceribandin|yi
|nivîskar=I.J. Singer|sernav=Fun a velt vos iz nishto mer|sal=1946|beş=yi:אַ ייד אַ מלמד פֿאַרגלוסט זיך צו ווערן אַ מלאך אום פּורים און פֿאַרלירט, נעבעך, אַן אויג<subst:ייד/ייִד,מלמד/מעלאַמעד,פּורים/פּורעם,מלאך/מאַלעך><t:A Jew, a teacher, decides to become an angel on Purim and, unfortunately, loses an eye>|jêgirtin=יעדן '''זמן''' אויפֿסניי, חול־המועד סוכות אָדער חול־המועד פּסח, בין איך געווען זייער האָפֿערדיק, וואָס מען האָט מיך גענומען איין מלמד און אָנגעדונגען צו אַן אַנדערן, גלײבנדיק, אַז דער נייער וועט זיין בעסער ווי דער אַלטער.|subst=זמן//זמאַן,חול־המועד//כאַלעמויעד,סוכות//סוקוס,פּסח//פּייסעך,מלמד//מעלאַמעד,נייער//נײַער|tercime=Every '''term''' anew, during the intermediary periods of Sukkot and Pesach, I was very confident in being taken from one melamed and given to another, believing the new one would be better than the old.}}
#* {{jêder-kitêb/ceribandin|ru
|sal=1983|nivîskar=w:Samuil Marshak|sernav=Баллада о королевском бутерброде<subst:бутер/бутэр><t:Ballad of the King's Bread>|orîjînal=The King's Breakfast|ji-aliyê=w:A. A. Milne|metna-orîjînal=en:"Nobody," / He whimpered, / "Could call me / A fussy man; / I only want / A little bit / Of butter for / My bread!"|zimanê-orîjînal=en|jêgirtin=— [[никто|Никто́]] [[не]] [[сказать|ска́жет]], [[бу́дто]] [[я]] / [[тиран|Тира́н]] [[и]] [[сумасбро́д]], / [[за|За]] [[то́]], [[что]] [[к]] [[чай|ча́ю]] [[я]] [[любить|люблю́]] / [[хороший|Хоро́ший]] '''бутербро́д'''.|subst=бутербро́д/бутэрбро́д|t="No one would say that I / Am a tyrant and madman, / For the fact that with my tea I like / A good '''bread-and-butter sandwich'''."}}
#* {{jêder-kitêb/ceribandin|ota
|sal=1922|sernav=ota:ادارهٔ حرب و سیاست<tr:Idare-i harb ve siyaset>|cih=Istanbul|weşanxane=ota:مطبعهٔ عثمانیه|rûpel=176|urlya-rûpelê=https://babel.hathitrust.org/cgi/pt?id=uc1.aa0006749931&view=1up&seq=182|versiyona-nû=translation of|ziman2=de|sal2=1922|2nivîskar=w:Erich Ludendorff|sernav2=Kriegsführung und Politik|cih2=Berlin|weşanxane2=E. S. Mittler & Sohn|rûpel2=199|urlya-rûpelê2=https://archive.org/details/kriegfhrungund00lude/page/199|jêgirtin=شرقی غالیچیا، و بوقوونیاده شمندوفر نقاباتی تأسس ایتدكدن صكره شرقده '''صلحی''' تأمین ایچون دینیهستر نهری جنوبندن روس — رومن جبههسنه قارشو حربه دوام ایتمك فكرندن فراغت ایتمك لازمكلیورایدی. بر قصقاچ تأثیری یاپهبیلمك ایچون بری قلاص غربندن یوقارهسهرهت اوزرینه و دیكری دینیهستر جنوبندن اولمق اوزره مضاعف بر تعرض اجراسی لازمكلیور ایده.|tr=Şarki Galiçya, ve Bukovinyada şömendüfer nakabatı tesis itdikden soñra şarkda sulhı temin içün Dinyester nehri cenubundan rus-rumen cebhesine karşu harbe devam itmek fikrinden feragat itmek lazım geliyor idi. Bir kıskaç tesiri yapabilmek içün biri Kalas garbından yukara-yı Seret üzerine ve diğeri Dinyester cenubundan olmak üzere muzaaf bir taarruz icrası lazım geliyor idi.|metna-orîjînal=de:Unsere weitere Absicht, nach Herstellung der Eisenbahnverbindungen in Ostgalizien und in der Bukowina zur Erzwingung des Friedens im Osten den Feldzug gegen die russisch-rumänische Front südlich des Dnjestr fortzusetzen, mußte aufgegeben werden. Er sollte in einem doppelten Angriff über den oberen Sereth westlich Galatz und hart südlich des Dnjestr bestehen und auch zangenartig wirken.|t=Our further intention, the military campaign against the Russian-Romanian front to be continued south of the Dniestr to force '''peace''' in the east, after procurement of the railroad connections in Eastern Galicia and in the Bukovina, had to be abandoned. It was supposed to consist in a double attack over the upper Siret west of Galați as well as hard south of the Dniestr and go like pincers.}}
#* {{jêder-kitêb/ceribandin|it
|sernav=L'œuvre complète de Tchouang-tseu|terciman=Liou Kia-hway<f:Hant:劉家槐><f:Pinyin:Liú Jiāhuái>|orîjînal={{w|Zhuangzi (book)|Zhuang-zi}}|ji-aliyê=莊周<t:Zhuang Zhou>|zimanê-orîjînal=och|sala-orîjînal=c. 300 {{BCE}}|sal=1969|sal2=1982|sala-weşandinê2=2010|versiyona-nû=translated as|terciman2=Carlo Laurenti; Christine Leverd|sernav2=Zhuang-zi [Chuang-tzu]|serî2=Gli Adelphi|cildê-seriyê2=41|beş2=Libertà naturale|tercimeya-beşê2=Natural Freedom|çap2=6th|cih2=Milan|weşanxane2={{w|Adelphi Edizioni}}|rûpel2=17|isbn2=978-88-459-0950-4|metna-orîjînal=och:之人也,物莫之傷,大浸稽天而不溺,大旱、金石流、土山焦而不熱。|jêgirtin=Se le acque si alzassero fino al cielo, non annegherebbero. Se la siccità '''liquefacesse''' i metalli e infiammasse le montagne, non ne sarebbero neppure sfiorati.|t=If the waters were to rise to the skies, they wouldn't drown. If drought were to '''liquefy''' metals, and set fire to the mountains, they wouldn't even be touched by it.}}
#* {{jêder-kitêb/ceribandin|fr
|zimanê-termê=en|sal=1677|nivîskar=Guy Miege [''i.e.'', [[w:fr:Guy Miège|Guy Miège]]]|made=HAGGESS|urlya-madeyê=https://books.google.com/books?id=YbWtTwVbLioC&pg=PP857|sernav=A New Dictionary French and English, with Another English and French; According to the Present Use, and Modern Orthography of the French. Inrich’d with New Words, Choice Phrases, and Apposite Proverbs; Digested into a Most Accurate Method; and Contrived for the Use Both of English and Foreiners|cih=London|weşanxane=Printed by Tho[mas] Dawks, for Thomas Basset, at the George, near [[w:Clifford's Inn|Cliffords-inn]], in [[w:Fleet Street|Fleetstreet]]|stûn=3|oclc=874955677|jêgirtin='''HAGGESS''', '''haggas''', or '''haggis''', ſort d'appret fait avec des herbes, du larde caupé menu, des épices, des œufs, & du fromage, le tout mis enſemble & bouilli dans une panſe de brebis.|t='''HAGGESS''', '''haggas''', or '''haggis''', a sort of preparation made with herbs, bacon, spices, eggs, cheese, all put together and boiled in a sheep's belly.|brackets=1}}
#*{{jêder-kovar/ceribandin|en|sernav=Art VIII.—Who are the True Conservatives?|kovar=The Quarterly Christian Spectator|cih=New Haven, Conn.|weşanxane=Published for the proprietor by Hezekiah Howe [...]|meh=November|sal=1838|cild=X|hejmar=IV|rûpel=608|urlya-rûpelê=https://books.google.com/books?id=S7MAAAAAMAAJ&pg=PA608|oclc=5090641|jêgirtin=They have guarded a few minds against '''sciolism''' and pretension in philosophy, and they have given a new and more imposing air to '''sciolism''' itself, and placed in the hands of the charlatan enchantments and magical arts, which hold fast its victims with a potency never granted to an inferior spell. There is no '''sciolism''' now which is more dangerous than that which is so very careful to inform us of its entire freedom from all that is superficial, {{...}}}}
#*{{jêder-kovar/ceribandin|en
|tarîx=March 8, 2023|paşnav=Wu|pêşnav=Huizhong|nivîskar2=Johnson Lai|sernav=Taiwan suspects Chinese ships cut islands’ internet cables|kovar={{w|The Washington Post}}|urlya-arşîvê=https://web.archive.org/web/20230311220100/https://www.washingtonpost.com/business/2023/03/08/matsu-taiwan-internet-cables-cut-china/d322fcae-bd70-11ed-9350-7c5fccd598ad_story.html|tarîxa-arşîvê=11 March 2023|issn=0190-8286|oclc=2269358|qisim=Business|tarîx2=April 18, 2023|sernav2=Chinese ships cut internet of Taiwan’s outlying islands|cure2=journal|kovar2={{w|AP News}}|urlya-arşîvê2=https://web.archive.org/web/20230308053048/https://apnews.com/article/matsu-taiwan-internet-cables-cut-china-65f10f5f73a346fa788436366d7a7c70|jêgirtin=Wang Chung Ming, the head of '''Lienchiang''' County, as the Matsu islands are officially called, said he and the legislator from Matsu went to Taipei shortly after the internet broke down to ask for help, and was told they would get priority in any future internet backup plans.{{...}}<br>Wang, the head of '''Lienchiang''' County, said he had mentioned the cables on a recent visit to China, where he had met an executive from China Mobile.}}
#*{{jêder-kovar/ceribandin|en
|sal=1994|meh=April|kovar=光華|tercimeya-kovar=Sinorama|url=https://books.google.com/books?id=GIRDAAAAYAAJ|cild=19|hejmar=1-6|weşanxane={{w|Government Information Office}}|issn=0256-9043|oclc=20549278|sal2=1994|meh2=April|2nivîskar=Yuan-ching Cheng|cure2=journal|sernav2=Kinmen- -From Battlefield to Tourist Destination|kovar2=台灣光華雜誌<t:Taiwan Panorama>|terciman2=Robert Taylor|urlya-arşîvê2=https://web.archive.org/web/20221021103917/https://www.taiwan-panorama.com/en/Articles/Details?Guid=c9335c12-7ec3-40e7-96be-33a81416a223|tarîxa-arşîvê2=21 October 2022|qisim2=Around Taiwan|jêgirtin=The temple to Colonel Li Kuang-chien lies near Hsiputou village in the northwest of Kinmen Island, around five kilometers from the town of '''Chincheng''' and next to the famous Hill 132.{{...}}<br>Because Kinmen was settled early, it bears many traces of the past. There are 21 listed historical monuments scattered all over the island. The only Grade I monument among them is the "Chiehhsiao Arch," commemorating Chiu Liang-kung's mother, located in East Houpu Street in the center of '''Chincheng'''.}}
== {{ziman|ku}} ==
=== Lêker ===
[[Wêne:Example.jpg|thumb|Mînak]]
{{lêker|ku}}
# [[testsdfsdafdasfas]]
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Mîr Kamûran di sala 1947 an de li '''Fransayê''' bicîh bû, û heta 1970ê li Xwendegeha zimanên rojhilatî ya Zaningeha Parîsê Profesorîya ziman û çanda Kurdî kir.|nivîskar=Kamuran A.Bedir Khan|nivîskar2=Josefe Bertolino|nivîskar3=Kendal Nezan|sernav=Institut Kurde De Paris Ferhenga Kurdî-Fransizî|cih=Istanbul|weşanxane={{w|Weşanên Avesta|Avesta}}|sal=2017|çap=1|rûpel=XXXIX|isbn=978-2-360134311}}
#* {{jêder-malper/ceribandin|ku|jêgirtin=Li gor amareke ajanseke selametiya xorakê, xelkê '''Fransa''' salane nêzîkî 4,000 ton Beqan dixwin, ku ew jî bi qasî goştê 40 nehengên şîn e. Ji bo dabînkirina pêwîstiya bazarê jî, li '''Fransayê''' kêlgeh û cihên taybet ên xwedîkirina Beqan hene. Beşek ji wêneyên di vê wênenûçeyê de, kêlgeha xwedîkirina Beqan li navçeyeke '''Fransayê''' ye, herwiha beşek ji wêneyan jî awayê patin û çêkirina xwarinê ji goştê Beqan di xwaringehan de û pêşkêşkirina wan bi mêvanan nîşan didin.|nivîskar=ciwan ikbal|sernav=Fransî di salekê de 4,000 ton Beqan dixwin|url=https://www.rudaw.net/kurmanci/world/101020201|dîrok=10.10.2020}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Bale awayeke dansê ye. Babetek ji şanoya musîkal e. Ji bo dansa baleyê, divê meriv cansivik û nerm be. Loma jî bale gelekî antremanê dixwaze û divê meriv di temenekî biçûk de dest pê bike. Di baleyê de, meriv li gor livên diyarkirî dansê dike. Hinek ji wan tu di wêne de dibînî. Ji ber ku bale li '''Fransayê''' bi pêş ketiye, loma navên livên wê jî bi [[fransî]] ne.|amadekar=Komxebat|sernav=Ansîklopediya Zarokan|cih=Denmark<br>|weşanxane={{w|Weşanên APECl|APEC}}|sal=2004|rûpel=25|isbn=91-89675-32-0}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Hekayêta Betal Kirina '''Xezeta''' Hetawî Kurd|sal=~1910|sala-weşandinê=2026|nivîskar=Mela Seîdê Şemdînanî|amadekar=David N. MacKenzie, Ergin Öpengin|url=http://books.google.com.tr/books?id=eqvrEQAAQBAJ&pg=PA383&dq=%22wan+xezetan%22+OR+%22xezet%C3%AA%22+OR+%22xezetin%22+OR+%22xezetek%C3%AA%22+OR+%22xezeta%22+OR+%22w%C3%AA+xezet%C3%AA%22+OR+%22xezetino%22+OR+%22xezetine%22&hl=&cd=2&source=gbs_api|sernav=Shamdinani Stories I: Kurdish Writings on the History and Culture of Central Kurdistan by Mullah Saʼid Shamdinani|weşanxane=BRILL|ISBN=9789004754430|rûpel=383}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Baş e, '''xezete''' dê bi çi ezmanî derkevit?|nivîskar=E. Rehmî Ji Mala Hekarîyan|amadekar=M. E. Bozarslan|sernav=Jîn: kovara Kurdî-Tirkî. Cild 1|weşanxane=Weşanxana Deng|cih=Uppsala|sala-weşandinê=1985|sal=1918|rûpel=310|isbn=978-91-970702-0-1}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=ligor{{sic}} danezanên '''xezete''' ên Turk hezar insan li kurdistan hatin kuştin ku ji % 70 jin û zaruk kal » pîrbûn|nivîskar=Hesen Hişyar Serdî|sernav=Dîtin û Bîrhatinên Min|weşanxane=Çapxana Emîral|cih=Beyrût|sal=1991|sala-weşandinê=2000|rûpel=380|url=https://bnk.institutkurde.org/images/pdf/CTNPMX4FR6.pdf}}
#* {{jêder-malper/ceribandin|ku|jêgirtin='''Xezeteyên''' Kurdî yên li Stenbolê derdiketin bên îstisnakirin, xezeteyekî millî û wetenî yê ku yekemîn car li Kurdistanê hatîye neşirkirin, ew jî xezeteya Zarê Kirmancî ye, ku min xwendîye.|nivîskar=Seîd Veroj|sernav=Mesûd Fanî (Dr. Mesûd Fanî Bîlgîlî)|websîte=Rûpela nû|url=https://www.rupelanu.com/seid-veroj-mesud-fani-dr-mesud-fani-bilgili-17258h.htm|tarîx=2021-05-04}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=me '''xezete''' da destê wî jî.|sal=2022|nivîskar=Abdurrahman Benek|url=http://books.google.com.tr/books?id=SdSUEAAAQBAJ&pg=PA33&dq=%22wan+xezetan%22+OR+%22xezet%C3%AA%22+OR+%22xezetin%22+OR+%22xezetek%C3%AA%22+OR+%22xezeta%22+OR+%22w%C3%AA+xezet%C3%AA%22+OR+%22xezetino%22+OR+%22xezetine%22&hl=&cd=3&source=gbs_api|sernav=Payiza Mezinan|weşanxane=Nûbihar|ISBN=9786059413749|rûpel=33}}
#* {{jêder-kitêb/ceribandin|ku|jêgirtin=Jêgirtinek ji kitêbekê.|nivîskar=Rojen Barnas|sernav=Sîlvan/ Silîvan / FARQÎN|sal=2013|weşanxane=Nûbihar|rûpel=189|isbn=9786055402884, 6055402882}}
#* {{jêder-malper/ceribandin|ku|nivîskar=Gazete Duvar|sernav=Şairê dubeytan: Baba Tahirê Uryan|websîte=https://www.gazeteduvar.com.tr|url=https://www.gazeteduvar.com.tr/saire-dubeytan-baba-tahire-uryan-haber-1753893|tarîx=2025-02-02|jêgirtin=adfasdf}}
#* {{jêder-kitêb/ceribandin|ku|sernav=Shamdinani Stories I|weşanxane=Brill|sal=2026|isbn=978-90-04-75443-0|url=https://brill.com/edcollbook-oa/title/74006|jêgirtin=test}}
# Maneya 3an.
#* {{jêder-malper/ceribandin|ckb|nivîskar=author|paşnav=last|pêşnav=first|lînka-nivîskar=authorlink|nivîskar2=author2|paşnav2=last2|pêşnav2=first2|lînka-nivîskar2=authorlink2|nivîskar3=author3|paşnav3=last3|pêşnav3=first3|lînka-nivîskar3=authorlink3|nivîskar4=author4|paşnav4=last4|pêşnav4=first4|lînka-nivîskar4=authorlink4|nivîskar5=author5|paşnav5=last5|pêşnav5=first5|lînka-nivîskar5=authorlink5|hevnivîskar=coauthors|jê-tê-girtin=quotee|terciman=translators|sernav=title|tercimeya-sernav=trans-title|xebat=work|trans-work=trans-work|url=url|urlya-arşîvê=archiveurl|tarîxa-arşîvê=archivedate|format=format|zimanê-eserê=fr|cih=location|weşanxane=publisher|sala-weşandinê=year_published|tarîx=2022-01-25|tarîxa-gihiştinê=2022-01-25|rûpel=5|urlya-rûpelê=pageurl|qisim=section|urlya-qismê=sectionurl|jêgirtin=passage|brackets=brackets|t=translation|hrf=lit|tr=transliteration|tr=transliteration|subst=subst}}
#* {{jêder-kitêb/ceribandin|ckb|nivîskar=author|lînka-nivîskar=authorlink|beş=chapter|edîtor=editors|sernav=title|url=url|urlya-arşîvê=archiveurl|tarîxa-arşîvê=archivedate|çap=edition|cih=location|weşanxane=publisher|tarîx=2022-05-09|isbn=isbn|oclc=oclc|cild=volume|qisim=section|urlya-qismê=sectionurl|rûpel=page|pages=pages|urlya-rûpelê=pageurl|jêgirtin=text|t=t|tr=}}
#* {{jêder-kovar/ceribandin|fr|sal=2025|nivîskar=author|sernav=title|kovar=journal|url=url|cild=volume|hejmar=issue|rûpel=page|jêgirtin=text|t=}}
2uhqd38jbb5gs2ztnk9o3vuwrc5anee
Bikarhêner:Wikihez/Gadget-start.css
2
1357891
6291585
6276331
2026-07-26T17:40:09Z
Wikihez
7344
6291585
css
text/css
/* ===================================================================
MediaWiki:Gadget-start.js — companion styles
Copy this file into MediaWiki:Common.css (or a dedicated gadget CSS
page) so that the JS gadget no longer needs to inject styles at runtime.
Requires MediaWiki:Gadget-Palette.css to be loaded for the
--wikt-palette-* custom properties.
=================================================================== */
/* ── Continue/submit button── */
.wiktku-btn {
padding: 0.4em 1.2em;
font-size: 1em;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
cursor: pointer;
}
.wiktku-btn:hover {
border-color: var(--wikt-palette-blue-9, #72777d);
}
.wiktku-btn:disabled {
cursor: default;
opacity: 0.5;
}
.wiktku-btn-progressive {
background: var(--wikt-palette-blue-9);
border-color: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
.wiktku-btn-progressive:hover:not(:disabled) {
filter: brightness(1.1);
}
.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);
}
.wiktku-start-step {
padding: 1em 0;
border-bottom: 1px solid #e0e0e0;
}
.wiktku-start-step-question {
font-size: 125%;
}
.wiktku-start-step label {
padding-right: 2em;
}
input[type="text"].wiktku-start-step-field {
width: 100%;
}
input[type="text"].wiktku-start-step-field.wiktku-start-narrow {
width: 20em;
}
select.wiktku-start-narrow {
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
#wiktku-start-spinner {
float: right;
margin-left: 1em;
}
#wiktku-start-form textarea.wiktku-start-step-field,
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield {
resize: both !important;
overflow: auto !important;
box-sizing: border-box !important;
vertical-align: top;
}
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield.wiktku-start-narrow {
width: 20em;
min-width: 8em;
height: calc(1em + 8px);
min-height: calc(1em + 8px);
padding-top: 2px !important;
padding-bottom: 2px !important;
line-height: 1.4 !important;
}
/* ── Mobile autocomplete widget ── */
.wiktku-native-select-wrap {
display: inline-block;
position: relative;
}
.wiktku-native-select-wrap input[type="search"] {
display: block;
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
ul.wiktku-ac-results {
position: absolute;
z-index: 9999;
top: 100%;
bottom: auto;
left: 0;
background: var(--wikt-palette-white);
border: 1px solid var(--wikt-palette-lightgrey);
margin: 0;
padding: 0;
list-style: none;
width: 20em;
max-width: 90vw;
max-height: 14em;
overflow-y: scroll;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
ul.wiktku-ac-results li {
padding: 6px 10px;
cursor: pointer;
font-size: 0.95em;
min-height: 2.4em;
display: flex;
align-items: center;
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
/* ── Wikitext preview panel ── */
#wiktku-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
white-space: pre-wrap;
word-break: break-word;
font-family: monospace;
font-size: 0.9em;
}
/* ── Live rendered preview panel (desktop only) ── */
#wiktku-live-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
word-break: break-word;
min-height: 2em;
}
/* ── Preview container (holds wikitext + live panels) ── */
#wiktku-preview-container {
display: flex;
flex-wrap: wrap;
gap: 1em;
margin-top: 0.5em;
}
#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 .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text,
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 2;
width: 100%;
box-sizing: border-box;
height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
min-height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
}
#wiktku-start-form .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 3;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 4;
}
.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.4 !important;
font-size: 0.9em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-tr {
background: var(--wikt-palette-cyan);
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-t {
background: var(--wikt-palette-palegreen);
}
/* ── Template selector row inside a sense block ── */
.wiktku-dfn-tpl-row {
margin-bottom: 4px;
}
.wiktku-dfn-tpl-select {
font-size: 0.9em;
}
input.wiktku-dfn-tpl-title {
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
.wiktku-pron-wrap,
.wiktku-etym-wrap {
display: flex;
flex-direction: column;
gap: 4px;
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select,
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
border: 1px solid var(--wikt-palette-lightgrey);
font-size: 0.9em;
width: 100%;
box-sizing: border-box;
padding: 3px 4px;
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select {
background: var(--wikt-palette-amber-1);
}
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
background: var(--wikt-palette-indigo-1);
}
.wiktku-pron-wrap textarea.wiktku-pron-text {
background: var(--wikt-palette-amber-0);
}
.wiktku-etym-wrap textarea.wiktku-etym-text {
background: var(--wikt-palette-indigo-0);
}
/* ── POS section tabs ── */
#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 10px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 3px 3px 0 0;
background: var(--wikt-palette-dullcyan);
cursor: pointer;
font-size: 0.9em;
user-select: none;
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-white);
border-bottom-color: var(--wikt-palette-white);
font-weight: bold;
}
.wiktku-lang-row-list {
margin: 0;
padding: 0;
list-style: none;
}
.wiktku-lang-row {
display: flex;
align-items: flex-start;
gap: 6px;
margin-bottom: 4px;
flex-wrap: wrap;
}
.wiktku-lang-row-words {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 3px;
}
/* ── Werger (translation) groups — one per gloss ── */
#wiktku-start-trans .wiktku-trans-group {
margin-bottom: 0.8em;
}
#wiktku-start-trans .wiktku-trans-group:last-child {
margin-bottom: 0;
}
#wiktku-start-trans .wiktku-trans-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
/* ── Bi zaravayên din (dialect) groups — one per gloss, mirrors Werger ── */
#wiktku-start-zarava .wiktku-zarava-group {
margin-bottom: 0.8em;
}
#wiktku-start-zarava .wiktku-zarava-group:last-child {
margin-bottom: 0;
}
#wiktku-start-zarava .wiktku-zarava-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
.wiktku-dfn-jeder-group {
margin-top: 4px;
}
/* Drag handle for reordering citation rows */
.wiktku-jeder-drag-handle {
display: inline-block;
cursor: grab;
color: var(--wikt-palette-lightgrey, #ccc);
font-size: 1.2em;
line-height: 1;
margin-right: 4px;
vertical-align: middle;
user-select: none;
}
.wiktku-jeder-drag-handle:active {
cursor: grabbing;
}
.wiktku-dfn-jeder-list .ui-sortable-placeholder {
border: 2px dashed var(--wikt-palette-lightgrey, #ccc);
background: transparent;
visibility: visible !important;
height: 2em;
}
.wiktku-dfn-quote-src-sel {
font-size: 0.9em;
margin-top: 4px;
display: block;
}
.wiktku-dfn-quote-src-fields {
display: flex;
flex-direction: column;
gap: 3px;
margin-top: 4px;
}
.wiktku-dfn-quote-src-cat-status {
display: block;
font-size: 0.85em;
color: var(--wikt-palette-lightgrey, #767676);
margin-top: 2px;
}
.wiktku-dfn-quote-src-row {
display: flex;
align-items: baseline;
gap: 4px;
flex-wrap: wrap;
}
.wiktku-dfn-quote-src-row label {
font-size: 0.85em;
white-space: nowrap;
min-width: 8em;
}
input.wiktku-dfn-quote-src-input {
flex: 1 1 12em;
max-width: 100%;
box-sizing: border-box;
}
@media (max-width: 720px) {
.wiktku-lang-row {
flex-direction: column;
}
#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-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 4;
font-size: 1em;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 6;
font-size: 1em;
}
#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: 3;
font-size: 1em;
}
.wiktku-dfn-nimune-sub {
margin-left: 0.5em;
padding-left: 0.4em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t {
max-width: calc(100vw - 6em);
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select,
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
font-size: 1em;
min-height: 2.4em;
}
.wiktku-dfn-quote-src-row label {
min-width: 6em;
}
input.wiktku-dfn-quote-src-input {
width: 100%;
flex: 1 1 100%;
}
}
@media (prefers-color-scheme: dark) {
ul.wiktku-ac-results {
background: var(--wikt-palette-white, #1e1e1e);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9, #3a6ea8);
color: #fff;
}
.wiktku-start-step {
border-bottom-color: var(--wikt-palette-lightgrey, #444);
}
.wiktku-addlang-notice {
background: var(--wikt-palette-paleblue, #1a2233);
border-color: var(--wikt-palette-lightgrey, #444);
}
#wiktku-preview-panel,
#wiktku-live-preview-panel {
background: var(--wikt-palette-paleblue, #1a2233);
border-color: var(--wikt-palette-lightgrey, #444);
}
#wiktku-section-tabs .wiktku-section-tab {
background: var(--wikt-palette-dullcyan, #1e2d3a);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-white, #121212);
border-bottom-color: var(--wikt-palette-white, #121212);
}
}
/* ── "Zimanek lê zêde bike" portlet link icon (Minerva/mobile) ── */
/* Minerva auto-generates the icon span class from the portlet link id. */
/* Uses Feather-core-edit.svg from Wikimedia Commons via CSS mask, matching */
/* the exact pattern Minerva uses for all its own icons. */
.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;
}
mi0hfvqirb3ighc3bud5diy1ab3hzbn
6291589
6291585
2026-07-26T18:33:01Z
Wikihez
7344
6291589
css
text/css
/* ===================================================================
MediaWiki:Gadget-start.js — companion styles
Copy this file into MediaWiki:Common.css (or a dedicated gadget CSS
page) so that the JS gadget no longer needs to inject styles at runtime.
Requires MediaWiki:Gadget-Palette.css to be loaded for the
--wikt-palette-* custom properties.
=================================================================== */
/* ── Continue/submit button── */
.wiktku-btn {
padding: 0.4em 1.2em;
font-size: 1em;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
cursor: pointer;
}
.wiktku-btn:hover {
border-color: var(--wikt-palette-blue-9, #72777d);
}
.wiktku-btn:disabled {
cursor: default;
opacity: 0.5;
}
.wiktku-btn-progressive {
background: var(--wikt-palette-blue-9);
border-color: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
.wiktku-btn-progressive:hover:not(:disabled) {
filter: brightness(1.1);
}
.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);
}
.wiktku-start-step {
padding: 1em 0;
border-bottom: 1px solid #e0e0e0;
}
.wiktku-start-step-question {
font-size: 125%;
}
.wiktku-start-step label {
padding-right: 2em;
}
input[type="text"].wiktku-start-step-field {
width: 100%;
}
input[type="text"].wiktku-start-step-field.wiktku-start-narrow {
width: 20em;
}
select.wiktku-start-narrow {
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.6em;
}
#wiktku-start-form textarea.wiktku-start-step-field,
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield {
resize: both !important;
overflow: auto !important;
box-sizing: border-box !important;
vertical-align: top;
}
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield.wiktku-start-narrow {
width: 20em;
min-width: 8em;
height: calc(1em + 8px);
min-height: calc(1em + 8px);
padding-top: 2px !important;
padding-bottom: 2px !important;
line-height: 1.4 !important;
}
/* ── Mobile autocomplete widget ── */
.wiktku-native-select-wrap {
display: inline-block;
position: relative;
}
.wiktku-native-select-wrap input[type="search"] {
display: block;
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
ul.wiktku-ac-results {
position: absolute;
z-index: 9999;
top: 100%;
bottom: auto;
left: 0;
background: var(--wikt-palette-white);
border: 1px solid var(--wikt-palette-lightgrey);
margin: 0;
padding: 0;
list-style: none;
width: 20em;
max-width: 90vw;
max-height: 14em;
overflow-y: scroll;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
ul.wiktku-ac-results li {
padding: 6px 10px;
cursor: pointer;
font-size: 0.95em;
min-height: 2.4em;
display: flex;
align-items: center;
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
/* ── Wikitext preview panel ── */
#wiktku-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
white-space: pre-wrap;
word-break: break-word;
font-family: monospace;
font-size: 0.9em;
}
/* ── Live rendered preview panel (desktop only) ── */
#wiktku-live-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
word-break: break-word;
min-height: 2em;
}
/* ── Preview container (holds wikitext + live panels) ── */
#wiktku-preview-container {
display: flex;
flex-wrap: wrap;
gap: 1em;
margin-top: 0.5em;
}
#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 .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text,
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 2;
width: 100%;
box-sizing: border-box;
height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
min-height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
}
#wiktku-start-form .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 3;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 4;
}
.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.4 !important;
font-size: 0.9em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-tr {
background: var(--wikt-palette-cyan);
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-t {
background: var(--wikt-palette-palegreen);
}
/* ── Template selector row inside a sense block ── */
.wiktku-dfn-tpl-row {
margin-bottom: 4px;
}
.wiktku-dfn-tpl-select {
font-size: 0.9em;
}
input.wiktku-dfn-tpl-title {
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
.wiktku-pron-wrap,
.wiktku-etym-wrap {
display: flex;
flex-direction: column;
gap: 4px;
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select,
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
border: 1px solid var(--wikt-palette-lightgrey);
font-size: 0.9em;
width: 100%;
box-sizing: border-box;
padding: 3px 4px;
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select {
background: var(--wikt-palette-amber-1);
}
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
background: var(--wikt-palette-indigo-1);
}
.wiktku-pron-wrap textarea.wiktku-pron-text {
background: var(--wikt-palette-amber-0);
}
.wiktku-etym-wrap textarea.wiktku-etym-text {
background: var(--wikt-palette-indigo-0);
}
/* ── POS section tabs ── */
#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 10px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 3px 3px 0 0;
background: var(--wikt-palette-dullcyan);
cursor: pointer;
font-size: 0.9em;
user-select: none;
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-white);
border-bottom-color: var(--wikt-palette-white);
font-weight: bold;
}
.wiktku-lang-row-list {
margin: 0;
padding: 0;
list-style: none;
}
.wiktku-lang-row {
display: flex;
align-items: flex-start;
gap: 6px;
margin-bottom: 4px;
flex-wrap: wrap;
}
.wiktku-lang-row-words {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 3px;
}
/* ── Werger (translation) groups — one per gloss ── */
#wiktku-start-trans .wiktku-trans-group {
margin-bottom: 0.8em;
}
#wiktku-start-trans .wiktku-trans-group:last-child {
margin-bottom: 0;
}
#wiktku-start-trans .wiktku-trans-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
/* ── Bi zaravayên din (dialect) groups — one per gloss, mirrors Werger ── */
#wiktku-start-zarava .wiktku-zarava-group {
margin-bottom: 0.8em;
}
#wiktku-start-zarava .wiktku-zarava-group:last-child {
margin-bottom: 0;
}
#wiktku-start-zarava .wiktku-zarava-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
.wiktku-dfn-jeder-group {
margin-top: 4px;
}
/* Drag handle for reordering citation rows */
.wiktku-jeder-drag-handle {
display: inline-block;
cursor: grab;
color: var(--wikt-palette-lightgrey, #ccc);
font-size: 1.2em;
line-height: 1;
margin-right: 4px;
vertical-align: middle;
user-select: none;
}
.wiktku-jeder-drag-handle:active {
cursor: grabbing;
}
.wiktku-dfn-jeder-list .ui-sortable-placeholder {
border: 2px dashed var(--wikt-palette-lightgrey, #ccc);
background: transparent;
visibility: visible !important;
height: 2em;
}
.wiktku-dfn-quote-src-sel {
font-size: 0.9em;
margin-top: 4px;
display: block;
}
.wiktku-dfn-quote-src-fields {
display: flex;
flex-direction: column;
gap: 3px;
margin-top: 4px;
}
.wiktku-dfn-quote-src-cat-status {
display: block;
font-size: 0.85em;
color: var(--wikt-palette-lightgrey, #767676);
margin-top: 2px;
}
.wiktku-dfn-quote-src-row {
display: flex;
align-items: baseline;
gap: 4px;
flex-wrap: wrap;
}
.wiktku-dfn-quote-src-row label {
font-size: 0.85em;
white-space: nowrap;
min-width: 8em;
}
input.wiktku-dfn-quote-src-input {
flex: 1 1 12em;
max-width: 100%;
box-sizing: border-box;
}
@media (max-width: 720px) {
.wiktku-lang-row {
flex-direction: column;
}
#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-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 4;
font-size: 1em;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 6;
font-size: 1em;
}
#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: 3;
font-size: 1em;
}
.wiktku-dfn-nimune-sub {
margin-left: 0.5em;
padding-left: 0.4em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t {
max-width: calc(100vw - 6em);
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select,
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
font-size: 1em;
min-height: 2.4em;
}
.wiktku-dfn-quote-src-row label {
min-width: 6em;
}
input.wiktku-dfn-quote-src-input {
width: 100%;
flex: 1 1 100%;
}
}
@media (prefers-color-scheme: dark) {
ul.wiktku-ac-results {
background: var(--wikt-palette-white, #1e1e1e);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9, #3a6ea8);
color: #fff;
}
.wiktku-start-step {
border-bottom-color: var(--wikt-palette-lightgrey, #444);
}
.wiktku-addlang-notice {
background: var(--wikt-palette-paleblue, #1a2233);
border-color: var(--wikt-palette-lightgrey, #444);
}
#wiktku-preview-panel,
#wiktku-live-preview-panel {
background: var(--wikt-palette-paleblue, #1a2233);
border-color: var(--wikt-palette-lightgrey, #444);
}
#wiktku-section-tabs .wiktku-section-tab {
background: var(--wikt-palette-dullcyan, #1e2d3a);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-white, #121212);
border-bottom-color: var(--wikt-palette-white, #121212);
}
}
/* ── "Zimanek lê zêde bike" portlet link icon (Minerva/mobile) ── */
/* Minerva auto-generates the icon span class from the portlet link id. */
/* Uses Feather-core-edit.svg from Wikimedia Commons via CSS mask, matching */
/* the exact pattern Minerva uses for all its own icons. */
.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;
}
ltjooka2zs7obi9g96b72our7b3cq9y
6291595
6291589
2026-07-26T19:31:06Z
Wikihez
7344
6291595
css
text/css
/* ===================================================================
MediaWiki:Gadget-start.js — companion styles
Copy this file into MediaWiki:Common.css (or a dedicated gadget CSS
page) so that the JS gadget no longer needs to inject styles at runtime.
Requires MediaWiki:Gadget-Palette.css to be loaded for the
--wikt-palette-* custom properties.
=================================================================== */
/* ── Continue/submit button── */
.wiktku-btn {
padding: 0.4em 1.2em;
font-size: 1em;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
cursor: pointer;
}
.wiktku-btn:hover {
border-color: var(--wikt-palette-blue-9, #72777d);
}
.wiktku-btn:disabled {
cursor: default;
opacity: 0.5;
}
.wiktku-btn-progressive {
background: var(--wikt-palette-blue-9);
border-color: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
.wiktku-btn-progressive:hover:not(:disabled) {
filter: brightness(1.1);
}
.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);
}
.wiktku-start-step {
padding: 1em 0;
border-bottom: 1px solid #e0e0e0;
}
.wiktku-start-step-question {
font-size: 125%;
}
.wiktku-start-step label {
padding-right: 2em;
}
input[type="text"].wiktku-start-step-field {
width: 100%;
}
input[type="text"].wiktku-start-step-field.wiktku-start-narrow {
width: 20em;
}
select.wiktku-start-narrow {
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.6em;
}
#wiktku-start-form textarea.wiktku-start-step-field,
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield {
resize: both !important;
overflow: auto !important;
box-sizing: border-box !important;
vertical-align: top;
}
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield.wiktku-start-narrow {
width: 20em;
min-width: 8em;
height: calc(1em + 8px);
min-height: calc(1em + 8px);
padding-top: 2px !important;
padding-bottom: 2px !important;
line-height: 1.4 !important;
}
/* ── Mobile autocomplete widget ── */
.wiktku-native-select-wrap {
display: inline-block;
position: relative;
}
.wiktku-native-select-wrap input[type="search"] {
display: block;
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
ul.wiktku-ac-results {
position: absolute;
z-index: 9999;
top: 100%;
bottom: auto;
left: 0;
background: var(--wikt-palette-white);
border: 1px solid var(--wikt-palette-lightgrey);
margin: 0;
padding: 0;
list-style: none;
width: 20em;
max-width: 90vw;
max-height: 14em;
overflow-y: scroll;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
ul.wiktku-ac-results li {
padding: 6px 10px;
cursor: pointer;
font-size: 0.95em;
min-height: 2.4em;
display: flex;
align-items: center;
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
/* ── Wikitext preview panel ── */
#wiktku-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
white-space: pre-wrap;
word-break: break-word;
font-family: monospace;
font-size: 0.9em;
}
/* ── Live rendered preview panel (desktop only) ── */
#wiktku-live-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
word-break: break-word;
min-height: 2em;
}
/* ── Preview container (holds wikitext + live panels) ── */
#wiktku-preview-container {
display: flex;
flex-wrap: wrap;
gap: 1em;
margin-top: 0.5em;
}
#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 .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text,
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 2;
width: 100%;
box-sizing: border-box;
height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
min-height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
}
#wiktku-start-form .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 3;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 4;
}
.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.4 !important;
font-size: 0.9em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-tr {
background: var(--wikt-palette-cyan);
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-t {
background: var(--wikt-palette-palegreen);
}
/* ── Template selector row inside a sense block ── */
.wiktku-dfn-tpl-row {
margin-bottom: 4px;
}
.wiktku-dfn-tpl-select {
font-size: 0.9em;
}
input.wiktku-dfn-tpl-title {
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
.wiktku-pron-wrap,
.wiktku-etym-wrap {
display: flex;
flex-direction: column;
gap: 4px;
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select,
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
border: 1px solid var(--wikt-palette-lightgrey);
font-size: 0.9em;
width: 100%;
box-sizing: border-box;
padding: 3px 4px;
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select {
background: var(--wikt-palette-amber-1);
}
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
background: var(--wikt-palette-indigo-1);
}
.wiktku-pron-wrap textarea.wiktku-pron-text {
background: var(--wikt-palette-amber-0);
}
.wiktku-etym-wrap textarea.wiktku-etym-text {
background: var(--wikt-palette-indigo-0);
}
/* ── POS section tabs ── */
#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 10px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 3px 3px 0 0;
background: var(--wikt-palette-dullcyan);
cursor: pointer;
font-size: 0.9em;
user-select: none;
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-white);
border-bottom-color: var(--wikt-palette-white);
font-weight: bold;
}
.wiktku-lang-row-list {
margin: 0;
padding: 0;
list-style: none;
}
.wiktku-lang-row {
display: flex;
align-items: flex-start;
gap: 6px;
margin-bottom: 4px;
flex-wrap: wrap;
}
.wiktku-lang-row-words {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 3px;
}
/* ── Werger (translation) groups — one per gloss ── */
#wiktku-start-trans .wiktku-trans-group {
margin-bottom: 0.8em;
}
#wiktku-start-trans .wiktku-trans-group:last-child {
margin-bottom: 0;
}
#wiktku-start-trans .wiktku-trans-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
/* ── Bi zaravayên din (dialect) groups — one per gloss, mirrors Werger ── */
#wiktku-start-zarava .wiktku-zarava-group {
margin-bottom: 0.8em;
}
#wiktku-start-zarava .wiktku-zarava-group:last-child {
margin-bottom: 0;
}
#wiktku-start-zarava .wiktku-zarava-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
.wiktku-dfn-jeder-group {
margin-top: 4px;
}
/* Drag handle for reordering citation rows */
.wiktku-jeder-drag-handle {
display: inline-block;
cursor: grab;
color: var(--wikt-palette-lightgrey, #ccc);
font-size: 1.2em;
line-height: 1;
margin-right: 4px;
vertical-align: middle;
user-select: none;
}
.wiktku-jeder-drag-handle:active {
cursor: grabbing;
}
.wiktku-dfn-jeder-list .ui-sortable-placeholder {
border: 2px dashed var(--wikt-palette-lightgrey, #ccc);
background: transparent;
visibility: visible !important;
height: 2em;
}
.wiktku-dfn-quote-src-sel {
font-size: 0.9em;
margin-top: 4px;
display: block;
}
.wiktku-dfn-quote-src-fields {
display: flex;
flex-direction: column;
gap: 3px;
margin-top: 4px;
}
.wiktku-dfn-quote-citoid {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.4em;
margin-bottom: 4px;
}
input.wiktku-dfn-quote-citoid-input {
flex: 1 1 24em;
width: 24em;
max-width: 100%;
box-sizing: border-box;
}
.wiktku-dfn-quote-src-cat-status {
display: block;
font-size: 0.85em;
color: var(--wikt-palette-lightgrey, #767676);
margin-top: 2px;
}
.wiktku-dfn-quote-src-row {
display: flex;
align-items: baseline;
gap: 4px;
flex-wrap: wrap;
}
.wiktku-dfn-quote-src-row label {
font-size: 0.85em;
white-space: nowrap;
min-width: 8em;
}
input.wiktku-dfn-quote-src-input {
flex: 1 1 12em;
max-width: 100%;
box-sizing: border-box;
}
@media (max-width: 720px) {
.wiktku-lang-row {
flex-direction: column;
}
#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-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 4;
font-size: 1em;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 6;
font-size: 1em;
}
#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: 3;
font-size: 1em;
}
.wiktku-dfn-nimune-sub {
margin-left: 0.5em;
padding-left: 0.4em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t {
max-width: calc(100vw - 6em);
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select,
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
font-size: 1em;
min-height: 2.4em;
}
.wiktku-dfn-quote-src-row label {
min-width: 6em;
}
input.wiktku-dfn-quote-src-input {
width: 100%;
flex: 1 1 100%;
}
input.wiktku-dfn-quote-citoid-input {
width: 100%;
flex: 1 1 100%;
}
}
@media (prefers-color-scheme: dark) {
ul.wiktku-ac-results {
background: var(--wikt-palette-white, #1e1e1e);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9, #3a6ea8);
color: #fff;
}
.wiktku-start-step {
border-bottom-color: var(--wikt-palette-lightgrey, #444);
}
.wiktku-addlang-notice {
background: var(--wikt-palette-paleblue, #1a2233);
border-color: var(--wikt-palette-lightgrey, #444);
}
#wiktku-preview-panel,
#wiktku-live-preview-panel {
background: var(--wikt-palette-paleblue, #1a2233);
border-color: var(--wikt-palette-lightgrey, #444);
}
#wiktku-section-tabs .wiktku-section-tab {
background: var(--wikt-palette-dullcyan, #1e2d3a);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-white, #121212);
border-bottom-color: var(--wikt-palette-white, #121212);
}
/* ── Chosen (desktop select-replacement) dropdowns ── */
/* Chosen ships its own light-only stylesheet (white backgrounds, grey
text) with no awareness of dark mode, so its closed box and open
dropdown panel stayed bright white here. `background:` (shorthand)
also clears Chosen's default background-image gradients. */
.chosen-container-single .chosen-single,
.chosen-container-multi .chosen-choices {
background: var(--wikt-palette-indigo-0, #100e36);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
.chosen-container-active .chosen-single,
.chosen-container-active.chosen-with-drop .chosen-single {
background: var(--wikt-palette-indigo-1, #1b1751);
border-color: var(--wikt-palette-blue-9, #3a6ea8);
color: inherit;
}
.chosen-container .chosen-drop {
background: var(--wikt-palette-indigo-0, #100e36);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
.chosen-container .chosen-results {
color: inherit;
}
.chosen-container .chosen-results li.highlighted {
background: var(--wikt-palette-blue-9, #3a6ea8);
color: #fff;
}
.chosen-container .chosen-results li.disabled-result,
.chosen-container .chosen-results li.result-selected {
color: var(--wikt-palette-lightgrey, #767676);
}
.chosen-container .chosen-results li.no-results {
background: var(--wikt-palette-indigo-1, #1b1751);
color: inherit;
}
.chosen-container .chosen-search input[type="text"] {
background: var(--wikt-palette-indigo-1, #1b1751);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
.chosen-container-multi .chosen-choices li.search-field input[type="text"] {
color: inherit;
}
.chosen-container-multi .chosen-choices li.search-choice {
background: var(--wikt-palette-indigo-1, #1b1751);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
}
/* ── "Zimanek lê zêde bike" portlet link icon (Minerva/mobile) ── */
/* Minerva auto-generates the icon span class from the portlet link id. */
/* Uses Feather-core-edit.svg from Wikimedia Commons via CSS mask, matching */
/* the exact pattern Minerva uses for all its own icons. */
.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;
}
cig5wslfqz9w2mvstxbhqmwz0k6wa8e
6291608
6291595
2026-07-26T20:51:26Z
Wikihez
7344
6291608
css
text/css
/* ===================================================================
MediaWiki:Gadget-start.js — companion styles
Copy this file into MediaWiki:Common.css (or a dedicated gadget CSS
page) so that the JS gadget no longer needs to inject styles at runtime.
Requires MediaWiki:Gadget-Palette.css to be loaded for the
--wikt-palette-* custom properties.
=================================================================== */
/* ── Continue/submit button── */
.wiktku-btn {
padding: 0.4em 1.2em;
font-size: 1em;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
cursor: pointer;
}
.wiktku-btn:hover {
border-color: var(--wikt-palette-blue-9, #72777d);
}
.wiktku-btn:disabled {
cursor: default;
opacity: 0.5;
}
.wiktku-btn-progressive {
background: var(--wikt-palette-blue-9);
border-color: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
.wiktku-btn-progressive:hover:not(:disabled) {
filter: brightness(1.1);
}
.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);
}
.wiktku-start-step {
padding: 1em 0;
border-bottom: 1px solid #e0e0e0;
}
.wiktku-start-step-question {
font-size: 125%;
}
.wiktku-start-step label {
padding-right: 2em;
}
input[type="text"].wiktku-start-step-field {
width: 100%;
}
input[type="text"].wiktku-start-step-field.wiktku-start-narrow {
width: 20em;
}
select.wiktku-start-narrow {
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.6em;
}
#wiktku-start-form textarea.wiktku-start-step-field,
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield {
resize: both !important;
overflow: auto !important;
box-sizing: border-box !important;
vertical-align: top;
}
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield.wiktku-start-narrow {
width: 20em;
min-width: 8em;
height: calc(1em + 8px);
min-height: calc(1em + 8px);
padding-top: 2px !important;
padding-bottom: 2px !important;
line-height: 1.4 !important;
}
/* ── Mobile autocomplete widget ── */
.wiktku-native-select-wrap {
display: inline-block;
position: relative;
}
.wiktku-native-select-wrap input[type="search"] {
display: block;
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
ul.wiktku-ac-results {
position: absolute;
z-index: 9999;
top: 100%;
bottom: auto;
left: 0;
background: var(--wikt-palette-white);
border: 1px solid var(--wikt-palette-lightgrey);
margin: 0;
padding: 0;
list-style: none;
width: 20em;
max-width: 90vw;
max-height: 14em;
overflow-y: scroll;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
ul.wiktku-ac-results li {
padding: 6px 10px;
cursor: pointer;
font-size: 0.95em;
min-height: 2.4em;
display: flex;
align-items: center;
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
/* ── Wikitext preview panel ── */
#wiktku-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
white-space: pre-wrap;
word-break: break-word;
font-family: monospace;
font-size: 0.9em;
}
/* ── Live rendered preview panel (desktop only) ── */
#wiktku-live-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
word-break: break-word;
min-height: 2em;
}
/* ── Preview container (holds wikitext + live panels) ── */
#wiktku-preview-container {
display: flex;
flex-wrap: wrap;
gap: 1em;
margin-top: 0.5em;
}
#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 .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text,
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 2;
width: 100%;
box-sizing: border-box;
height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
min-height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
}
#wiktku-start-form .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 3;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 4;
}
.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.4 !important;
font-size: 0.9em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-tr {
background: var(--wikt-palette-cyan);
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-t {
background: var(--wikt-palette-palegreen);
}
/* ── Template selector row inside a sense block ── */
.wiktku-dfn-tpl-row {
margin-bottom: 4px;
}
.wiktku-dfn-tpl-select {
font-size: 0.9em;
}
input.wiktku-dfn-tpl-title {
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
.wiktku-pron-wrap,
.wiktku-etym-wrap {
display: flex;
flex-direction: column;
gap: 4px;
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select,
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
border: 1px solid var(--wikt-palette-lightgrey);
font-size: 0.9em;
width: 100%;
box-sizing: border-box;
padding: 3px 4px;
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select {
background: var(--wikt-palette-amber-1);
}
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
background: var(--wikt-palette-lightindigo-1);
}
.wiktku-pron-wrap textarea.wiktku-pron-text {
background: var(--wikt-palette-amber-0);
}
.wiktku-etym-wrap textarea.wiktku-etym-text {
background: var(--wikt-palette-lightindigo-0);
}
/* ── POS section tabs ── */
#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 10px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 3px 3px 0 0;
background: var(--wikt-palette-dullcyan);
cursor: pointer;
font-size: 0.9em;
user-select: none;
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-white);
border-bottom-color: var(--wikt-palette-white);
font-weight: bold;
}
.wiktku-lang-row-list {
margin: 0;
padding: 0;
list-style: none;
}
.wiktku-lang-row {
display: flex;
align-items: flex-start;
gap: 6px;
margin-bottom: 4px;
flex-wrap: wrap;
}
.wiktku-lang-row-words {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 3px;
}
/* ── Werger (translation) groups — one per gloss ── */
#wiktku-start-trans .wiktku-trans-group {
margin-bottom: 0.8em;
}
#wiktku-start-trans .wiktku-trans-group:last-child {
margin-bottom: 0;
}
#wiktku-start-trans .wiktku-trans-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
/* ── Bi zaravayên din (dialect) groups — one per gloss, mirrors Werger ── */
#wiktku-start-zarava .wiktku-zarava-group {
margin-bottom: 0.8em;
}
#wiktku-start-zarava .wiktku-zarava-group:last-child {
margin-bottom: 0;
}
#wiktku-start-zarava .wiktku-zarava-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
.wiktku-dfn-jeder-group {
margin-top: 4px;
}
/* Drag handle for reordering citation rows */
.wiktku-jeder-drag-handle {
display: inline-block;
cursor: grab;
color: var(--wikt-palette-lightgrey, #ccc);
font-size: 1.2em;
line-height: 1;
margin-right: 4px;
vertical-align: middle;
user-select: none;
}
.wiktku-jeder-drag-handle:active {
cursor: grabbing;
}
.wiktku-dfn-jeder-list .ui-sortable-placeholder {
border: 2px dashed var(--wikt-palette-lightgrey, #ccc);
background: transparent;
visibility: visible !important;
height: 2em;
}
.wiktku-dfn-quote-src-sel {
font-size: 0.9em;
margin-top: 4px;
display: block;
}
.wiktku-dfn-quote-src-fields {
display: flex;
flex-direction: column;
gap: 3px;
margin-top: 4px;
}
.wiktku-dfn-quote-citoid {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.4em;
margin-bottom: 4px;
}
input.wiktku-dfn-quote-citoid-input {
flex: 1 1 24em;
width: 24em;
max-width: 100%;
box-sizing: border-box;
}
.wiktku-dfn-quote-src-cat-status {
display: block;
font-size: 0.85em;
color: var(--wikt-palette-lightgrey, #767676);
margin-top: 2px;
}
.wiktku-dfn-quote-src-row {
display: flex;
align-items: baseline;
gap: 4px;
flex-wrap: wrap;
}
.wiktku-dfn-quote-src-row label {
font-size: 0.85em;
white-space: nowrap;
min-width: 8em;
}
input.wiktku-dfn-quote-src-input {
flex: 1 1 12em;
max-width: 100%;
box-sizing: border-box;
}
@media (max-width: 720px) {
.wiktku-lang-row {
flex-direction: column;
}
#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-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 4;
font-size: 1em;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 6;
font-size: 1em;
}
#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: 3;
font-size: 1em;
}
.wiktku-dfn-nimune-sub {
margin-left: 0.5em;
padding-left: 0.4em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t {
max-width: calc(100vw - 6em);
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select,
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
font-size: 1em;
min-height: 2.4em;
}
.wiktku-dfn-quote-src-row label {
min-width: 6em;
}
input.wiktku-dfn-quote-src-input {
width: 100%;
flex: 1 1 100%;
}
input.wiktku-dfn-quote-citoid-input {
width: 100%;
flex: 1 1 100%;
}
}
@media (prefers-color-scheme: dark) {
ul.wiktku-ac-results {
background: var(--wikt-palette-white, #1e1e1e);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9, #3a6ea8);
color: #fff;
}
.wiktku-start-step {
border-bottom-color: var(--wikt-palette-lightgrey, #444);
}
.wiktku-addlang-notice {
background: var(--wikt-palette-paleblue, #1a2233);
border-color: var(--wikt-palette-lightgrey, #444);
}
#wiktku-preview-panel,
#wiktku-live-preview-panel {
background: var(--wikt-palette-paleblue, #1a2233);
border-color: var(--wikt-palette-lightgrey, #444);
}
#wiktku-section-tabs .wiktku-section-tab {
background: var(--wikt-palette-dullcyan, #1e2d3a);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-white, #121212);
border-bottom-color: var(--wikt-palette-white, #121212);
}
/* ── Chosen (desktop select-replacement) dropdowns ── */
/* Chosen ships its own light-only stylesheet (white backgrounds, grey
text) with no awareness of dark mode, so its closed box and open
dropdown panel stayed bright white here. `background:` (shorthand)
also clears Chosen's default background-image gradients. */
.chosen-container-single .chosen-single,
.chosen-container-multi .chosen-choices {
background: var(--wikt-palette-lightindigo-0, #100e36);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
.chosen-container-active .chosen-single,
.chosen-container-active.chosen-with-drop .chosen-single {
background: var(--wikt-palette-lightindigo-1, #1b1751);
border-color: var(--wikt-palette-blue-9, #3a6ea8);
color: inherit;
}
.chosen-container .chosen-drop {
background: var(--wikt-palette-lightindigo-0, #100e36);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
.chosen-container .chosen-results {
color: inherit;
}
.chosen-container .chosen-results li.highlighted {
background: var(--wikt-palette-blue-9, #3a6ea8);
color: #fff;
}
.chosen-container .chosen-results li.disabled-result,
.chosen-container .chosen-results li.result-selected {
color: var(--wikt-palette-lightgrey, #767676);
}
.chosen-container .chosen-results li.no-results {
background: var(--wikt-palette-lightindigo-1, #1b1751);
color: inherit;
}
.chosen-container .chosen-search input[type="text"] {
background: var(--wikt-palette-lightindigo-1, #1b1751);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
.chosen-container-multi .chosen-choices li.search-field input[type="text"] {
color: inherit;
}
.chosen-container-multi .chosen-choices li.search-choice {
background: var(--wikt-palette-lightindigo-1, #1b1751);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
}
/* ── "Zimanek lê zêde bike" portlet link icon (Minerva/mobile) ── */
/* Minerva auto-generates the icon span class from the portlet link id. */
/* Uses Feather-core-edit.svg from Wikimedia Commons via CSS mask, matching */
/* the exact pattern Minerva uses for all its own icons. */
.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;
}
3kv2yomt27oq5en490gddcf7nbwmhj1
6291609
6291608
2026-07-26T20:53:46Z
Wikihez
7344
6291609
css
text/css
/* ===================================================================
MediaWiki:Gadget-start.js — companion styles
Copy this file into MediaWiki:Common.css (or a dedicated gadget CSS
page) so that the JS gadget no longer needs to inject styles at runtime.
Requires MediaWiki:Gadget-Palette.css to be loaded for the
--wikt-palette-* custom properties.
=================================================================== */
/* ── Continue/submit button── */
.wiktku-btn {
padding: 0.4em 1.2em;
font-size: 1em;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
cursor: pointer;
}
.wiktku-btn:hover {
border-color: var(--wikt-palette-blue-9, #72777d);
}
.wiktku-btn:disabled {
cursor: default;
opacity: 0.5;
}
.wiktku-btn-progressive {
background: var(--wikt-palette-blue-9);
border-color: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
.wiktku-btn-progressive:hover:not(:disabled) {
filter: brightness(1.1);
}
.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);
}
.wiktku-start-step {
padding: 1em 0;
border-bottom: 1px solid #e0e0e0;
}
.wiktku-start-step-question {
font-size: 125%;
}
.wiktku-start-step label {
padding-right: 2em;
}
input[type="text"].wiktku-start-step-field {
width: 100%;
}
input[type="text"].wiktku-start-step-field.wiktku-start-narrow {
width: 20em;
}
select.wiktku-start-narrow {
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.6em;
}
#wiktku-start-form textarea.wiktku-start-step-field,
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield {
resize: both !important;
overflow: auto !important;
box-sizing: border-box !important;
vertical-align: top;
}
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield.wiktku-start-narrow {
width: 20em;
min-width: 8em;
height: calc(1em + 8px);
min-height: calc(1em + 8px);
padding-top: 2px !important;
padding-bottom: 2px !important;
line-height: 1.4 !important;
}
/* ── Mobile autocomplete widget ── */
.wiktku-native-select-wrap {
display: inline-block;
position: relative;
}
.wiktku-native-select-wrap input[type="search"] {
display: block;
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
ul.wiktku-ac-results {
position: absolute;
z-index: 9999;
top: 100%;
bottom: auto;
left: 0;
background: var(--wikt-palette-white);
border: 1px solid var(--wikt-palette-lightgrey);
margin: 0;
padding: 0;
list-style: none;
width: 20em;
max-width: 90vw;
max-height: 14em;
overflow-y: scroll;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
ul.wiktku-ac-results li {
padding: 6px 10px;
cursor: pointer;
font-size: 0.95em;
min-height: 2.4em;
display: flex;
align-items: center;
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
/* ── Wikitext preview panel ── */
#wiktku-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
white-space: pre-wrap;
word-break: break-word;
font-family: monospace;
font-size: 0.9em;
}
/* ── Live rendered preview panel (desktop only) ── */
#wiktku-live-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
word-break: break-word;
min-height: 2em;
}
/* ── Preview container (holds wikitext + live panels) ── */
#wiktku-preview-container {
display: flex;
flex-wrap: wrap;
gap: 1em;
margin-top: 0.5em;
}
#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 .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text,
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 2;
width: 100%;
box-sizing: border-box;
height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
min-height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
}
#wiktku-start-form .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 3;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 4;
}
.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.4 !important;
font-size: 0.9em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-tr {
background: var(--wikt-palette-cyan);
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-t {
background: var(--wikt-palette-palegreen);
}
/* ── Template selector row inside a sense block ── */
.wiktku-dfn-tpl-row {
margin-bottom: 4px;
}
.wiktku-dfn-tpl-select {
font-size: 0.9em;
}
input.wiktku-dfn-tpl-title {
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
.wiktku-pron-wrap,
.wiktku-etym-wrap {
display: flex;
flex-direction: column;
gap: 4px;
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select,
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
border: 1px solid var(--wikt-palette-lightgrey);
font-size: 0.9em;
width: 100%;
box-sizing: border-box;
padding: 3px 4px;
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select {
background: var(--wikt-palette-amber-1);
}
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
background: var(--wikt-palette-lightindigo-5);
}
.wiktku-pron-wrap textarea.wiktku-pron-text {
background: var(--wikt-palette-amber-0);
}
.wiktku-etym-wrap textarea.wiktku-etym-text {
background: var(--wikt-palette-lightindigo-5);
}
/* ── POS section tabs ── */
#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 10px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 3px 3px 0 0;
background: var(--wikt-palette-dullcyan);
cursor: pointer;
font-size: 0.9em;
user-select: none;
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-white);
border-bottom-color: var(--wikt-palette-white);
font-weight: bold;
}
.wiktku-lang-row-list {
margin: 0;
padding: 0;
list-style: none;
}
.wiktku-lang-row {
display: flex;
align-items: flex-start;
gap: 6px;
margin-bottom: 4px;
flex-wrap: wrap;
}
.wiktku-lang-row-words {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 3px;
}
/* ── Werger (translation) groups — one per gloss ── */
#wiktku-start-trans .wiktku-trans-group {
margin-bottom: 0.8em;
}
#wiktku-start-trans .wiktku-trans-group:last-child {
margin-bottom: 0;
}
#wiktku-start-trans .wiktku-trans-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
/* ── Bi zaravayên din (dialect) groups — one per gloss, mirrors Werger ── */
#wiktku-start-zarava .wiktku-zarava-group {
margin-bottom: 0.8em;
}
#wiktku-start-zarava .wiktku-zarava-group:last-child {
margin-bottom: 0;
}
#wiktku-start-zarava .wiktku-zarava-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
.wiktku-dfn-jeder-group {
margin-top: 4px;
}
/* Drag handle for reordering citation rows */
.wiktku-jeder-drag-handle {
display: inline-block;
cursor: grab;
color: var(--wikt-palette-lightgrey, #ccc);
font-size: 1.2em;
line-height: 1;
margin-right: 4px;
vertical-align: middle;
user-select: none;
}
.wiktku-jeder-drag-handle:active {
cursor: grabbing;
}
.wiktku-dfn-jeder-list .ui-sortable-placeholder {
border: 2px dashed var(--wikt-palette-lightgrey, #ccc);
background: transparent;
visibility: visible !important;
height: 2em;
}
.wiktku-dfn-quote-src-sel {
font-size: 0.9em;
margin-top: 4px;
display: block;
}
.wiktku-dfn-quote-src-fields {
display: flex;
flex-direction: column;
gap: 3px;
margin-top: 4px;
}
.wiktku-dfn-quote-citoid {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.4em;
margin-bottom: 4px;
}
input.wiktku-dfn-quote-citoid-input {
flex: 1 1 24em;
width: 24em;
max-width: 100%;
box-sizing: border-box;
}
.wiktku-dfn-quote-src-cat-status {
display: block;
font-size: 0.85em;
color: var(--wikt-palette-lightgrey, #767676);
margin-top: 2px;
}
.wiktku-dfn-quote-src-row {
display: flex;
align-items: baseline;
gap: 4px;
flex-wrap: wrap;
}
.wiktku-dfn-quote-src-row label {
font-size: 0.85em;
white-space: nowrap;
min-width: 8em;
}
input.wiktku-dfn-quote-src-input {
flex: 1 1 12em;
max-width: 100%;
box-sizing: border-box;
}
@media (max-width: 720px) {
.wiktku-lang-row {
flex-direction: column;
}
#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-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 4;
font-size: 1em;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 6;
font-size: 1em;
}
#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: 3;
font-size: 1em;
}
.wiktku-dfn-nimune-sub {
margin-left: 0.5em;
padding-left: 0.4em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t {
max-width: calc(100vw - 6em);
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select,
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
font-size: 1em;
min-height: 2.4em;
}
.wiktku-dfn-quote-src-row label {
min-width: 6em;
}
input.wiktku-dfn-quote-src-input {
width: 100%;
flex: 1 1 100%;
}
input.wiktku-dfn-quote-citoid-input {
width: 100%;
flex: 1 1 100%;
}
}
@media (prefers-color-scheme: dark) {
ul.wiktku-ac-results {
background: var(--wikt-palette-white, #1e1e1e);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9, #3a6ea8);
color: #fff;
}
.wiktku-start-step {
border-bottom-color: var(--wikt-palette-lightgrey, #444);
}
.wiktku-addlang-notice {
background: var(--wikt-palette-paleblue, #1a2233);
border-color: var(--wikt-palette-lightgrey, #444);
}
#wiktku-preview-panel,
#wiktku-live-preview-panel {
background: var(--wikt-palette-paleblue, #1a2233);
border-color: var(--wikt-palette-lightgrey, #444);
}
#wiktku-section-tabs .wiktku-section-tab {
background: var(--wikt-palette-dullcyan, #1e2d3a);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-white, #121212);
border-bottom-color: var(--wikt-palette-white, #121212);
}
/* ── Chosen (desktop select-replacement) dropdowns ── */
/* Chosen ships its own light-only stylesheet (white backgrounds, grey
text) with no awareness of dark mode, so its closed box and open
dropdown panel stayed bright white here. `background:` (shorthand)
also clears Chosen's default background-image gradients. */
.chosen-container-single .chosen-single,
.chosen-container-multi .chosen-choices {
background: var(--wikt-palette-lightindigo-5, #100e36);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
.chosen-container-active .chosen-single,
.chosen-container-active.chosen-with-drop .chosen-single {
background: var(--wikt-palette-lightindigo-5, #1b1751);
border-color: var(--wikt-palette-blue-9, #3a6ea8);
color: inherit;
}
.chosen-container .chosen-drop {
background: var(--wikt-palette-lightindigo-5, #100e36);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
.chosen-container .chosen-results {
color: inherit;
}
.chosen-container .chosen-results li.highlighted {
background: var(--wikt-palette-blue-9, #3a6ea8);
color: #fff;
}
.chosen-container .chosen-results li.disabled-result,
.chosen-container .chosen-results li.result-selected {
color: var(--wikt-palette-lightgrey, #767676);
}
.chosen-container .chosen-results li.no-results {
background: var(--wikt-palette-lightindigo-5, #1b1751);
color: inherit;
}
.chosen-container .chosen-search input[type="text"] {
background: var(--wikt-palette-lightindigo-5, #1b1751);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
.chosen-container-multi .chosen-choices li.search-field input[type="text"] {
color: inherit;
}
.chosen-container-multi .chosen-choices li.search-choice {
background: var(--wikt-palette-lightindigo-5, #1b1751);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
}
/* ── "Zimanek lê zêde bike" portlet link icon (Minerva/mobile) ── */
/* Minerva auto-generates the icon span class from the portlet link id. */
/* Uses Feather-core-edit.svg from Wikimedia Commons via CSS mask, matching */
/* the exact pattern Minerva uses for all its own icons. */
.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;
}
ahfgooalkfmculw96v8p06m1zux7ek3
6291610
6291609
2026-07-26T21:25:31Z
Wikihez
7344
6291610
css
text/css
/* ===================================================================
MediaWiki:Gadget-start.js — companion styles
Copy this file into MediaWiki:Common.css (or a dedicated gadget CSS
page) so that the JS gadget no longer needs to inject styles at runtime.
Requires MediaWiki:Gadget-Palette.css to be loaded for the
--wikt-palette-* custom properties.
=================================================================== */
/* ── Continue/submit button── */
.wiktku-btn {
padding: 0.4em 1.2em;
font-size: 1em;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
cursor: pointer;
}
.wiktku-btn:hover {
border-color: var(--wikt-palette-blue-9, #72777d);
}
.wiktku-btn:disabled {
cursor: default;
opacity: 0.5;
}
.wiktku-btn-progressive {
background: var(--wikt-palette-blue-9);
border-color: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
.wiktku-btn-progressive:hover:not(:disabled) {
filter: brightness(1.1);
}
.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);
}
.wiktku-start-step {
padding: 1em 0;
border-bottom: 1px solid #e0e0e0;
}
.wiktku-start-step-question {
font-size: 125%;
}
.wiktku-start-step label {
padding-right: 2em;
}
input[type="text"].wiktku-start-step-field {
width: 100%;
}
input[type="text"].wiktku-start-step-field.wiktku-start-narrow {
width: 20em;
}
select.wiktku-start-narrow {
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.6em;
}
#wiktku-start-form textarea.wiktku-start-step-field,
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield {
resize: both !important;
overflow: auto !important;
box-sizing: border-box !important;
vertical-align: top;
}
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield.wiktku-start-narrow {
width: 20em;
min-width: 8em;
height: calc(1em + 8px);
min-height: calc(1em + 8px);
padding-top: 2px !important;
padding-bottom: 2px !important;
line-height: 1.4 !important;
}
/* ── Mobile autocomplete widget ── */
.wiktku-native-select-wrap {
display: inline-block;
position: relative;
}
.wiktku-native-select-wrap input[type="search"] {
display: block;
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
ul.wiktku-ac-results {
position: absolute;
z-index: 9999;
top: 100%;
bottom: auto;
left: 0;
background: var(--wikt-palette-white);
border: 1px solid var(--wikt-palette-lightgrey);
margin: 0;
padding: 0;
list-style: none;
width: 20em;
max-width: 90vw;
max-height: 14em;
overflow-y: scroll;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
ul.wiktku-ac-results li {
padding: 6px 10px;
cursor: pointer;
font-size: 0.95em;
min-height: 2.4em;
display: flex;
align-items: center;
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
/* ── Wikitext preview panel ── */
#wiktku-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
white-space: pre-wrap;
word-break: break-word;
font-family: monospace;
font-size: 0.9em;
}
/* ── Live rendered preview panel (desktop only) ── */
#wiktku-live-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
word-break: break-word;
min-height: 2em;
}
/* ── Preview container (holds wikitext + live panels) ── */
#wiktku-preview-container {
display: flex;
flex-wrap: wrap;
gap: 1em;
margin-top: 0.5em;
}
#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 .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text,
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 2;
width: 100%;
box-sizing: border-box;
height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
min-height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
}
#wiktku-start-form .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 3;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 4;
}
.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.4 !important;
font-size: 0.9em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-tr {
background: var(--wikt-palette-cyan);
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-t {
background: var(--wikt-palette-palegreen);
}
/* ── Template selector row inside a sense block ── */
.wiktku-dfn-tpl-row {
margin-bottom: 4px;
}
.wiktku-dfn-tpl-select {
font-size: 0.9em;
}
input.wiktku-dfn-tpl-title {
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
.wiktku-pron-wrap,
.wiktku-etym-wrap {
display: flex;
flex-direction: column;
gap: 4px;
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select,
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
border: 1px solid var(--wikt-palette-lightgrey);
font-size: 0.9em;
width: 100%;
box-sizing: border-box;
padding: 3px 4px;
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select {
background: var(--wikt-palette-amber-1);
}
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
background: var(--wikt-palette-cyan-3);
}
.wiktku-pron-wrap textarea.wiktku-pron-text {
background: var(--wikt-palette-amber-0);
}
.wiktku-etym-wrap textarea.wiktku-etym-text {
background: var(--wikt-palette-cyan-3);
}
/* ── POS section tabs ── */
#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 10px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 3px 3px 0 0;
background: var(--wikt-palette-dullcyan);
cursor: pointer;
font-size: 0.9em;
user-select: none;
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-white);
border-bottom-color: var(--wikt-palette-white);
font-weight: bold;
}
.wiktku-lang-row-list {
margin: 0;
padding: 0;
list-style: none;
}
.wiktku-lang-row {
display: flex;
align-items: flex-start;
gap: 6px;
margin-bottom: 4px;
flex-wrap: wrap;
}
.wiktku-lang-row-words {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 3px;
}
/* ── Werger (translation) groups — one per gloss ── */
#wiktku-start-trans .wiktku-trans-group {
margin-bottom: 0.8em;
}
#wiktku-start-trans .wiktku-trans-group:last-child {
margin-bottom: 0;
}
#wiktku-start-trans .wiktku-trans-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
/* ── Bi zaravayên din (dialect) groups — one per gloss, mirrors Werger ── */
#wiktku-start-zarava .wiktku-zarava-group {
margin-bottom: 0.8em;
}
#wiktku-start-zarava .wiktku-zarava-group:last-child {
margin-bottom: 0;
}
#wiktku-start-zarava .wiktku-zarava-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
.wiktku-dfn-jeder-group {
margin-top: 4px;
}
/* Drag handle for reordering citation rows */
.wiktku-jeder-drag-handle {
display: inline-block;
cursor: grab;
color: var(--wikt-palette-lightgrey, #ccc);
font-size: 1.2em;
line-height: 1;
margin-right: 4px;
vertical-align: middle;
user-select: none;
}
.wiktku-jeder-drag-handle:active {
cursor: grabbing;
}
.wiktku-dfn-jeder-list .ui-sortable-placeholder {
border: 2px dashed var(--wikt-palette-lightgrey, #ccc);
background: transparent;
visibility: visible !important;
height: 2em;
}
.wiktku-dfn-quote-src-sel {
font-size: 0.9em;
margin-top: 4px;
display: block;
}
.wiktku-dfn-quote-src-fields {
display: flex;
flex-direction: column;
gap: 3px;
margin-top: 4px;
}
.wiktku-dfn-quote-citoid {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.4em;
margin-bottom: 4px;
}
input.wiktku-dfn-quote-citoid-input {
flex: 1 1 24em;
width: 24em;
max-width: 100%;
box-sizing: border-box;
}
.wiktku-dfn-quote-src-cat-status {
display: block;
font-size: 0.85em;
color: var(--wikt-palette-lightgrey, #767676);
margin-top: 2px;
}
.wiktku-dfn-quote-src-row {
display: flex;
align-items: baseline;
gap: 4px;
flex-wrap: wrap;
}
.wiktku-dfn-quote-src-row label {
font-size: 0.85em;
white-space: nowrap;
min-width: 8em;
}
input.wiktku-dfn-quote-src-input {
flex: 1 1 12em;
max-width: 100%;
box-sizing: border-box;
}
@media (max-width: 720px) {
.wiktku-lang-row {
flex-direction: column;
}
#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-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 4;
font-size: 1em;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 6;
font-size: 1em;
}
#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: 3;
font-size: 1em;
}
.wiktku-dfn-nimune-sub {
margin-left: 0.5em;
padding-left: 0.4em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t {
max-width: calc(100vw - 6em);
}
.wiktku-pron-wrap select.wiktku-tpl-insert-select,
.wiktku-etym-wrap select.wiktku-tpl-insert-select {
font-size: 1em;
min-height: 2.4em;
}
.wiktku-dfn-quote-src-row label {
min-width: 6em;
}
input.wiktku-dfn-quote-src-input {
width: 100%;
flex: 1 1 100%;
}
input.wiktku-dfn-quote-citoid-input {
width: 100%;
flex: 1 1 100%;
}
}
@media (prefers-color-scheme: dark) {
ul.wiktku-ac-results {
background: var(--wikt-palette-white, #1e1e1e);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9, #3a6ea8);
color: #fff;
}
.wiktku-start-step {
border-bottom-color: var(--wikt-palette-lightgrey, #444);
}
.wiktku-addlang-notice {
background: var(--wikt-palette-paleblue, #1a2233);
border-color: var(--wikt-palette-lightgrey, #444);
}
#wiktku-preview-panel,
#wiktku-live-preview-panel {
background: var(--wikt-palette-paleblue, #1a2233);
border-color: var(--wikt-palette-lightgrey, #444);
}
#wiktku-section-tabs .wiktku-section-tab {
background: var(--wikt-palette-dullcyan, #1e2d3a);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-white, #121212);
border-bottom-color: var(--wikt-palette-white, #121212);
}
/* ── Chosen (desktop select-replacement) dropdowns ── */
/* Chosen ships its own light-only stylesheet (white backgrounds, grey
text) with no awareness of dark mode, so its closed box and open
dropdown panel stayed bright white here. `background:` (shorthand)
also clears Chosen's default background-image gradients. */
.chosen-container-single .chosen-single,
.chosen-container-multi .chosen-choices {
background: var(--wikt-palette-cyan-3, #100e36);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
.chosen-container-active .chosen-single,
.chosen-container-active.chosen-with-drop .chosen-single {
background: var(--wikt-palette-cyan-3, #1b1751);
border-color: var(--wikt-palette-blue-9, #3a6ea8);
color: inherit;
}
.chosen-container .chosen-drop {
background: var(--wikt-palette-cyan-3, #100e36);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
.chosen-container .chosen-results {
color: inherit;
}
.chosen-container .chosen-results li.highlighted {
background: var(--wikt-palette-blue-9, #3a6ea8);
color: #fff;
}
.chosen-container .chosen-results li.disabled-result,
.chosen-container .chosen-results li.result-selected {
color: var(--wikt-palette-lightgrey, #767676);
}
.chosen-container .chosen-results li.no-results {
background: var(--wikt-palette-cyan-3, #1b1751);
color: inherit;
}
.chosen-container .chosen-search input[type="text"] {
background: var(--wikt-palette-cyan-3, #1b1751);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
.chosen-container-multi .chosen-choices li.search-field input[type="text"] {
color: inherit;
}
.chosen-container-multi .chosen-choices li.search-choice {
background: var(--wikt-palette-cyan-3, #1b1751);
border-color: var(--wikt-palette-lightgrey, #444);
color: inherit;
}
}
/* ── "Zimanek lê zêde bike" portlet link icon (Minerva/mobile) ── */
/* Minerva auto-generates the icon span class from the portlet link id. */
/* Uses Feather-core-edit.svg from Wikimedia Commons via CSS mask, matching */
/* the exact pattern Minerva uses for all its own icons. */
.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;
}
30sek4n4x7jpchydlb1gbokyyztpo26
6291629
6291610
2026-07-27T09:36:42Z
Wikihez
7344
6291629
css
text/css
/* ===================================================================
MediaWiki:Gadget-start.js — companion styles
Copy this file into MediaWiki:Common.css (or a dedicated gadget CSS
page). start.js injects no styles at runtime, so this file is the
only source of truth for the gadget's appearance — it must be
deployed together with start.js.
Requires MediaWiki:Gadget-Palette.css to be loaded for the
--wikt-palette-* custom properties. Palette.css already re-defines
every var for night mode (html.skin-theme-clientpref-night / -os),
so rules built from those vars need no dark-mode counterpart here.
=================================================================== */
/* ── Continue/submit button── */
.wiktku-btn {
padding: 0.4em 1.2em;
font-size: 1em;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
cursor: pointer;
}
.wiktku-btn:hover {
border-color: var(--wikt-palette-blue-9, #72777d);
}
.wiktku-btn:disabled {
cursor: default;
opacity: 0.5;
}
.wiktku-btn-progressive {
background: var(--wikt-palette-blue-9);
border-color: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
.wiktku-btn-progressive:hover:not(:disabled) {
filter: brightness(1.1);
}
.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);
}
.wiktku-start-step {
padding: 1em 0;
border-bottom: 1px solid var(--wikt-palette-grey-3, #e0e0e0);
}
.wiktku-start-step-question {
font-size: 125%;
}
.wiktku-start-step label {
padding-right: 2em;
}
input[type="text"].wiktku-start-step-field {
width: 100%;
}
input[type="text"].wiktku-start-step-field.wiktku-start-narrow {
width: 20em;
}
/* Only reachable on a select that opted out with .wiktku-plain-select —
every other select in the form is hidden behind the searchable picker,
which carries this width itself (see input.wiktku-ac-input). */
select.wiktku-start-narrow {
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.6em;
}
#wiktku-start-form textarea.wiktku-start-step-field,
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield {
resize: both !important;
overflow: auto !important;
box-sizing: border-box !important;
vertical-align: top;
}
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield.wiktku-start-narrow {
width: 20em;
min-width: 8em;
height: calc(1em + 8px);
min-height: calc(1em + 8px);
padding-top: 2px !important;
padding-bottom: 2px !important;
line-height: 1.4 !important;
}
/* ===================================================================
Searchable dropdown (wrapNativeSelect in start.js)
Used for EVERY dropdown in the form, desktop and mobile alike --
jquery.chosen is no longer loaded, so there is no .chosen-* CSS here.
Structure: span.wiktku-native-select-wrap
> input.wiktku-ac-input (the search box)
> ul.wiktku-ac-results (the result list)
> select (hidden, holds the value)
=================================================================== */
.wiktku-native-select-wrap {
display: inline-block;
position: relative;
vertical-align: top;
}
/* The search box stands in for the native select, so it matches the
width the 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: 4px 6px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
color: inherit;
font-size: inherit;
font-family: inherit;
}
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;
}
/* Safari/Chrome draw their own clear button and magnifier on
input[type=search]; drop them so the box matches every other field. */
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 ── */
/* top/bottom and max-height are set inline by showResults(), which flips
the list above the box when there is more room up there. The values
here are the defaults it starts from. */
ul.wiktku-ac-results {
position: absolute;
z-index: 9999;
top: 100%;
bottom: auto;
left: 0;
margin: 0;
padding: 0;
list-style: none;
width: 20em;
max-width: 90vw;
max-height: 14em;
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
background: var(--wikt-palette-white);
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
ul.wiktku-ac-results li {
margin: 0;
padding: 5px 8px;
cursor: pointer;
font-size: 0.95em;
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* .wiktku-ac-active is the keyboard cursor (arrow keys); hover mirrors it
so mouse and keyboard highlight look the same. :active covers the touch
press state on mobile. */
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, and no hover highlight (it sticks
after a tap on touch screens). */
@media (pointer: coarse) {
ul.wiktku-ac-results li {
padding: 6px 10px;
min-height: 2.4em;
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.4em;
}
}
/* The insert-a-template pickers keep their tinted backgrounds now that the
search box, not the <select>, is what the user actually 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 {
background: var(--wikt-palette-amber-1);
font-size: 0.9em;
}
.wiktku-etym-wrap .wiktku-native-select-wrap input.wiktku-ac-input {
background: var(--wikt-palette-cyan-3);
font-size: 0.9em;
}
.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.9em;
}
/* Language/dialect rows (Ziman, Dûnde, Werger, Zarava) — the picker has to
line up with the word fields sitting next to it. */
.wiktku-lang-row .wiktku-native-select-wrap {
flex: 0 0 auto;
}
/* ── Wikitext preview panel ── */
#wiktku-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
white-space: pre-wrap;
word-break: break-word;
font-family: monospace;
font-size: 0.9em;
}
/* ── Live rendered preview panel (desktop only) ── */
#wiktku-live-preview-panel {
margin-top: 1em;
padding: 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
word-break: break-word;
min-height: 2em;
}
/* ── Preview container (holds wikitext + live panels) ── */
#wiktku-preview-container {
display: flex;
flex-wrap: wrap;
gap: 1em;
margin-top: 0.5em;
}
#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 .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text,
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 2;
width: 100%;
box-sizing: border-box;
height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
min-height: calc(var(--wiktku-ta-rows) * 1.4em + 8px);
}
#wiktku-start-form .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 3;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 4;
}
.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.4 !important;
font-size: 0.9em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-tr {
background: var(--wikt-palette-cyan);
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t,
.wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-t {
background: var(--wikt-palette-palegreen);
}
/* ── Template selector row inside a sense block ── */
.wiktku-dfn-tpl-row {
margin-bottom: 4px;
}
input.wiktku-dfn-tpl-title {
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
.wiktku-pron-wrap,
.wiktku-etym-wrap {
display: flex;
flex-direction: column;
gap: 4px;
}
.wiktku-pron-wrap textarea.wiktku-pron-text {
background: var(--wikt-palette-amber-0);
}
.wiktku-etym-wrap textarea.wiktku-etym-text {
background: var(--wikt-palette-cyan-3);
}
/* ── POS section tabs ── */
#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 10px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 3px 3px 0 0;
background: var(--wikt-palette-dullcyan);
cursor: pointer;
font-size: 0.9em;
user-select: none;
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-white);
border-bottom-color: var(--wikt-palette-white);
font-weight: bold;
}
.wiktku-lang-row-list {
margin: 0;
padding: 0;
list-style: none;
}
.wiktku-lang-row {
display: flex;
align-items: flex-start;
gap: 6px;
margin-bottom: 4px;
flex-wrap: wrap;
}
.wiktku-lang-row-words {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 3px;
}
/* ── Werger (translation) groups — one per gloss ── */
#wiktku-start-trans .wiktku-trans-group {
margin-bottom: 0.8em;
}
#wiktku-start-trans .wiktku-trans-group:last-child {
margin-bottom: 0;
}
#wiktku-start-trans .wiktku-trans-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
/* ── Bi zaravayên din (dialect) groups — one per gloss, mirrors Werger ── */
#wiktku-start-zarava .wiktku-zarava-group {
margin-bottom: 0.8em;
}
#wiktku-start-zarava .wiktku-zarava-group:last-child {
margin-bottom: 0;
}
#wiktku-start-zarava .wiktku-zarava-group-label {
font-weight: bold;
font-size: 0.9em;
margin-bottom: 3px;
}
.wiktku-dfn-jeder-group {
margin-top: 4px;
}
/* Drag handle for reordering citation rows */
.wiktku-jeder-drag-handle {
display: inline-block;
cursor: grab;
color: var(--wikt-palette-lightgrey, #ccc);
font-size: 1.2em;
line-height: 1;
margin-right: 4px;
vertical-align: middle;
user-select: none;
}
.wiktku-jeder-drag-handle:active {
cursor: grabbing;
}
.wiktku-dfn-jeder-list .ui-sortable-placeholder {
border: 2px dashed var(--wikt-palette-lightgrey, #ccc);
background: transparent;
visibility: visible !important;
height: 2em;
}
.wiktku-dfn-quote-src-sel {
font-size: 0.9em;
margin-top: 4px;
display: block;
}
.wiktku-dfn-quote-src-fields {
display: flex;
flex-direction: column;
gap: 3px;
margin-top: 4px;
}
.wiktku-dfn-quote-citoid {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.4em;
margin-bottom: 4px;
}
input.wiktku-dfn-quote-citoid-input {
flex: 1 1 24em;
width: 24em;
max-width: 100%;
box-sizing: border-box;
}
.wiktku-dfn-quote-src-cat-status {
display: block;
font-size: 0.85em;
color: var(--wikt-palette-lightgrey, #767676);
margin-top: 2px;
}
.wiktku-dfn-quote-src-row {
display: flex;
align-items: baseline;
gap: 4px;
flex-wrap: wrap;
}
.wiktku-dfn-quote-src-row label {
font-size: 0.85em;
white-space: nowrap;
min-width: 8em;
}
input.wiktku-dfn-quote-src-input {
flex: 1 1 12em;
max-width: 100%;
box-sizing: border-box;
}
@media (max-width: 720px) {
.wiktku-lang-row {
flex-direction: column;
}
/* One column, so let the search box and its list use the full width. */
.wiktku-native-select-wrap,
input.wiktku-ac-input,
ul.wiktku-ac-results {
width: 100%;
max-width: 100%;
}
#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-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 4;
font-size: 1em;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 6;
font-size: 1em;
}
#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: 3;
font-size: 1em;
}
.wiktku-dfn-nimune-sub {
margin-left: 0.5em;
padding-left: 0.4em;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t {
max-width: calc(100vw - 6em);
}
.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.4em;
}
.wiktku-dfn-quote-src-row label {
min-width: 6em;
}
input.wiktku-dfn-quote-src-input {
width: 100%;
flex: 1 1 100%;
}
input.wiktku-dfn-quote-citoid-input {
width: 100%;
flex: 1 1 100%;
}
}
/* ===================================================================
Night mode
MediaWiki:Gadget-Palette.css already re-defines every --wikt-palette-*
var for night mode, on html.skin-theme-clientpref-night and (for the
"follow OS" setting) html.skin-theme-clientpref-os under a dark OS
preference. So any rule above that is built from those vars flips by
itself and needs nothing here.
What used to live here was a bare @media (prefers-color-scheme: dark)
block. That was the wrong hook twice over: it fired even when the
reader had explicitly chosen light mode on-wiki, and almost every rule
in it just restated the light rule with the same var. The only genuine
exceptions were the .chosen-* overrides, and jquery.chosen is no
longer loaded at all.
Keep this hook for anything that has to be a real colour rather than a
palette var — here, only the panel's edge, because a drop shadow does
nothing against an equally dark page. The two selectors below mirror
Palette.css exactly.
Note what is deliberately NOT here: li:active sets
`color: var(--wikt-palette-white)`, and that var flips to #101418 at
night, so the pressed row is dark-on-light in night mode all by itself
(9.3:1). Re-stating that colour here as --wikt-palette-black would
make it light-on-light and drop it to 1.7:1.
=================================================================== */
@media screen {
html.skin-theme-clientpref-night .wiktku-ac-results {
/* Lift the panel off an equally dark page: the light-mode drop shadow
is invisible on a dark background, so lean on the border instead. */
border-color: var(--wikt-palette-grey-5);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.6);
}
}
@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 2px 8px rgba(0, 0, 0, 0.6);
}
}
/* ── "Zimanek lê zêde bike" portlet link icon (Minerva/mobile) ── */
/* Minerva auto-generates the icon span class from the portlet link id. */
/* Uses Feather-core-edit.svg from Wikimedia Commons via CSS mask, matching */
/* the exact pattern Minerva uses for all its own icons. */
.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;
}
47oav1a0k6ppvcz3kvywsui1sxqeqoa
Bikarhêner:Wikihez/skrîpt/js/lastedited.js
2
1358139
6291612
6276828
2026-07-26T22:40:01Z
Wikihez
7344
6291612
javascript
text/javascript
/**
* Script for ku.wiktionary.org
* Automatically shows last edit time for members of "Kategorî:Madeyên ku bên jêbirin"
* Highlights items older than 7 days.
* Dark-mode aware: responds to prefers-color-scheme and MediaWiki's
* Vector 2022 "night" skin theme class.
*/
(function($, mw) {
// Only run on the specific deletion category page
if (mw.config.get('wgPageName') !== 'Kategorî:Madeyên_ku_bên_jêbirin') {
return;
}
function injectStyles() {
if (document.getElementById('last-edit-tag-styles')) return;
var css = ''
+ '.last-edit-tag {'
+ ' font-size: 0.82em;'
+ ' margin-left: 8px;'
+ ' padding: 1px 4px;'
+ ' border-radius: 3px;'
+ ' display: inline-block;'
+ '}'
+ '.last-edit-tag--recent {'
+ ' color: #555;'
+ ' background-color: #f1f1f1;'
+ ' border: 1px solid #ddd;'
+ '}'
+ '.last-edit-tag--old {'
+ ' background-color: #d33;'
+ ' color: #fff;'
+ ' font-weight: bold;'
+ '}'
+ '.last-edit-item--old {'
+ ' background-color: #fff0f0;'
+ ' border-left: 4px solid #d33;'
+ ' padding-left: 6px;'
+ ' margin-bottom: 1px;'
+ '}'
// System dark mode (browser-level)
+ '@media (prefers-color-scheme: dark) {'
+ ' .last-edit-tag--recent {'
+ ' color: #bbb;'
+ ' background-color: #2a2a2a;'
+ ' border: 1px solid #444;'
+ ' }'
+ ' .last-edit-tag--old {'
+ ' background-color: #ff6b6b;'
+ ' color: #1a1a1a;'
+ ' }'
+ ' .last-edit-item--old {'
+ ' background-color: #3a1f1f;'
+ ' border-left: 4px solid #ff6b6b;'
+ ' }'
+ '}'
// MediaWiki Vector 2022 "night" theme (explicit user preference,
// takes precedence over the OS-level media query above)
+ '.skin-theme-clientpref-night .last-edit-tag--recent {'
+ ' color: #bbb;'
+ ' background-color: #2a2a2a;'
+ ' border: 1px solid #444;'
+ '}'
+ '.skin-theme-clientpref-night .last-edit-tag--old {'
+ ' background-color: #ff6b6b;'
+ ' color: #1a1a1a;'
+ '}'
+ '.skin-theme-clientpref-night .last-edit-item--old {'
+ ' background-color: #3a1f1f;'
+ ' border-left: 4px solid #ff6b6b;'
+ '}'
// Explicit "day" preference should always win over OS dark mode
+ '.skin-theme-clientpref-day .last-edit-tag--recent {'
+ ' color: #555;'
+ ' background-color: #f1f1f1;'
+ ' border: 1px solid #ddd;'
+ '}'
+ '.skin-theme-clientpref-day .last-edit-tag--old {'
+ ' background-color: #d33;'
+ ' color: #fff;'
+ '}'
+ '.skin-theme-clientpref-day .last-edit-item--old {'
+ ' background-color: #fff0f0;'
+ ' border-left: 4px solid #d33;'
+ '}';
var $style = $('<style>')
.attr('id', 'last-edit-tag-styles')
.text(css);
$('head').append($style);
}
function getRelativeKurdishTime(timestamp) {
var now = new Date();
var past = new Date(timestamp);
var diffInSeconds = Math.floor((now - past) / 1000);
// 7 days = 604800 seconds
var isOld = diffInSeconds > 604800;
var text = "niha";
var intervals = [
{ label: 'salan', seconds: 31536000 },
{ label: 'mehan', seconds: 2592000 },
{ label: 'rojan', seconds: 86400 },
{ label: 'saetan', seconds: 3600 },
{ label: 'deqîqeyan', seconds: 60 }
];
for (var i = 0; i < intervals.length; i++) {
var interval = intervals[i];
var count = Math.floor(diffInSeconds / interval.seconds);
if (count >= 1) {
text = "berî " + count + " " + interval.label;
break;
}
}
return { text: text, isOld: isOld };
}
function autoShowLastEdits() {
var $links = $('.mw-category-generated a').not('.external');
var titles = [];
var linkMap = {};
// Find all links in the category list
$links.each(function() {
var title = $(this).attr('title');
if (title) {
titles.push(title);
if (!linkMap[title]) linkMap[title] = [];
linkMap[title].push($(this));
}
});
if (titles.length === 0) return;
var api = new mw.Api();
// Batch requests to API (50 titles at a time)
for (var i = 0; i < titles.length; i += 50) {
var batch = titles.slice(i, i + 50);
api.get({
action: 'query',
prop: 'revisions',
titles: batch,
rvprop: 'timestamp',
formatversion: 2
}).done(function(data) {
if (!data.query || !data.query.pages) return;
data.query.pages.forEach(function(page) {
if (page.revisions && page.revisions[0]) {
var timeData = getRelativeKurdishTime(page.revisions[0].timestamp);
if (linkMap[page.title]) {
linkMap[page.title].forEach(function($el) {
// Prevent duplicate tags if script runs twice
if ($el.parent().find('.last-edit-tag').length) return;
var $tag = $('<span>')
.addClass('last-edit-tag')
.addClass(timeData.isOld ? 'last-edit-tag--old' : 'last-edit-tag--recent')
.text('(' + timeData.text + ')');
if (timeData.isOld) {
$el.closest('li').addClass('last-edit-item--old');
}
$el.after($tag);
});
}
}
});
});
}
}
// Run automatically when the page is ready
$(document).ready(function() {
injectStyles();
autoShowLastEdits();
});
})(jQuery, mediaWiki);
sgfdr6593yiw5m8w67pe5y4de0ein69
Wîkîferheng:Nû
4
1358184
6291600
2026-07-26T20:09:34Z
Wikihez
7344
Ji bo [[MediaWiki:Gadget-start.js]] hat beralîkirin
6291600
wikitext
text/x-wiki
#BERALÎKIRIN [[MediaWiki:Gadget-start.js]]
fbmznk14jksvcz4gz220otwksjm7c6s
Wîkîferheng:Palette
4
1358185
6291601
2026-07-26T20:14:22Z
Wikihez
7344
[[:en:Wiktionary:Palette]] hat kopîkirin ([[Bikarhêner:Wikihez/skrîpt/js/kopi-enWikt.js|kopi-enWikt.js]])
6291601
wikitext
text/x-wiki
{{shortcut|WT:P}}
Wiktionary defines a CSS palette of colors that can be used by templates. Using the palette variables instead of hardcoded colors provides automatic support for dark mode, and provides a consistent set of colors for use by templates to maintain a more consistent look-and-feel. The code may be found at [[MediaWiki:Gadget-Palette.css]].
==Colors==
There are two main groups of colors.
Those colors without numbers in their names are used by core templates (i.e. templates that are used widely across pages and are not designed for specific languages or groups of languages) and as text colors.
Those colors with numbers in their names are the numbered colors, intended for use as background or border colors, e.g. in inflection tables. They are also used by {{şx|inflection-table-top}}. The numbered colors consist of 16 base colors with 10 contrast levels each. These colors were derived algorithmically: see [[Wiktionary:Palette/numbered]] for details.
The numbered base colors are red, scarlet, orange, amber, yellow, lime, green, teal, cyan, blue, indigo, purple, magenta, rose, brown and grey, and the indices go from 0 to 9. For example, a blue color that is the closest to the background color has the name <code>blue-0</code>, resulting in the full name <code>--wikt-palette-blue-0</code>. Desaturated shades of all base colors except grey can be obtained by prefixing <code>grey-</code>, e.g. <code>grey-blue-0</code>.
==Usage==
Palette colors can be used via CSS using <code>var(...)</code>.
'''Bad:'''
<syntaxhighlight lang="html">
<span style="color: #660000">some red text</span>
</syntaxhighlight>
<span style="color: #660000">some red text</span>
'''Better:'''
<syntaxhighlight lang="html">
<span style="color: var(--wikt-palette-darkred)">some red text</span>
</syntaxhighlight>
<span style="color: var(--wikt-palette-darkred)">some red text</span>
The first snippet will be difficult to read in dark mode, whereas the bottom snippet will automatically invert its colors.
It is possible to provide a fallback color which would theoretically activate if for whatever reason the palette variables are absent. This looks something like <code>var(--wikt-palette-blue-0, #f8fafe)</code>. When converting older templates, the pre-existing color is sometimes left as the fallback, but this is not required.
===Recommendations===
When using numbered colors, the colors with index 0, or simply the default page background color, should be used as the background color for main table cells to ensure maximum contrast. Headers should use colors with the index 1, 2, 3 or 4 as the background color depending on their level and required levels of contrast.
Colors with indices 7, 8 and 9 should ideally not be used as background colors for elements with text, unless the text color is inverted from the default color. They are mainly useful as text or border colors.
===Shorthand===
With numbered colors, there also exists a shorthand. You can use it by specifying one of the base colors using the <code>data-wikt-palette-p</code> attribute, e.g. <code>data-wikt-palette-p="red"</code>. Then, any elements within such a tag can use <code>--wikt-p-{0-9}</code> to use the palette color <code>--wikt-palette-red-{0-9}</code>, as well as <code>--wikt-p-g-{0-9}</code> to use the palette color <code>--wikt-palette-grey-red-{0-9}</code>. With the base color <code>grey</code>, both give the same color.
==Table==
{{box-top|Table of palette variables (long)}}
{{#invoke:palette|show}}
{{box-bottom}}
[[Category:Wiktionary]]
hy8qlhhb49akx4rdscqcdo757xjpx2t
6291607
6291601
2026-07-26T20:21:29Z
Wikihez
7344
/* */ bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re
6291607
wikitext
text/x-wiki
{{shortcut|WT:P}}
Wiktionary defines a CSS palette of colors that can be used by templates. Using the palette variables instead of hardcoded colors provides automatic support for dark mode, and provides a consistent set of colors for use by templates to maintain a more consistent look-and-feel. The code may be found at [[MediaWiki:Gadget-Palette.css]].
==Colors==
There are two main groups of colors.
Those colors without numbers in their names are used by core templates (i.e. templates that are used widely across pages and are not designed for specific languages or groups of languages) and as text colors.
Those colors with numbers in their names are the numbered colors, intended for use as background or border colors, e.g. in inflection tables. They are also used by {{şx|inflection-table-top}}. The numbered colors consist of 16 base colors with 10 contrast levels each. These colors were derived algorithmically: see [[Wiktionary:Palette/numbered]] for details.
The numbered base colors are red, scarlet, orange, amber, yellow, lime, green, teal, cyan, blue, indigo, purple, magenta, rose, brown and grey, and the indices go from 0 to 9. For example, a blue color that is the closest to the background color has the name <code>blue-0</code>, resulting in the full name <code>--wikt-palette-blue-0</code>. Desaturated shades of all base colors except grey can be obtained by prefixing <code>grey-</code>, e.g. <code>grey-blue-0</code>.
==Usage==
Palette colors can be used via CSS using <code>var(...)</code>.
'''Bad:'''
<syntaxhighlight lang="html">
<span style="color: #660000">some red text</span>
</syntaxhighlight>
<span style="color: #660000">some red text</span>
'''Better:'''
<syntaxhighlight lang="html">
<span style="color: var(--wikt-palette-darkred)">some red text</span>
</syntaxhighlight>
<span style="color: var(--wikt-palette-darkred)">some red text</span>
The first snippet will be difficult to read in dark mode, whereas the bottom snippet will automatically invert its colors.
It is possible to provide a fallback color which would theoretically activate if for whatever reason the palette variables are absent. This looks something like <code>var(--wikt-palette-blue-0, #f8fafe)</code>. When converting older templates, the pre-existing color is sometimes left as the fallback, but this is not required.
===Recommendations===
When using numbered colors, the colors with index 0, or simply the default page background color, should be used as the background color for main table cells to ensure maximum contrast. Headers should use colors with the index 1, 2, 3 or 4 as the background color depending on their level and required levels of contrast.
Colors with indices 7, 8 and 9 should ideally not be used as background colors for elements with text, unless the text color is inverted from the default color. They are mainly useful as text or border colors.
===Shorthand===
With numbered colors, there also exists a shorthand. You can use it by specifying one of the base colors using the <code>data-wikt-palette-p</code> attribute, e.g. <code>data-wikt-palette-p="red"</code>. Then, any elements within such a tag can use <code>--wikt-p-{0-9}</code> to use the palette color <code>--wikt-palette-red-{0-9}</code>, as well as <code>--wikt-p-g-{0-9}</code> to use the palette color <code>--wikt-palette-grey-red-{0-9}</code>. With the base color <code>grey</code>, both give the same color.
==Table==
{{veşêre-ser|Table of palette variables (long)}}
{{#invoke:palette|show}}
{{veşêre-bin}}
[[Category:Wîkîferheng]]
5o1zxyyi0si2miv8f2xiwmlvv38guka
Wîkîferheng:Palette/numbered
4
1358186
6291602
2026-07-26T20:15:10Z
Wikihez
7344
[[:en:Wiktionary:Palette/numbered]] hat kopîkirin ([[Bikarhêner:Wikihez/skrîpt/js/kopi-enWikt.js|kopi-enWikt.js]])
6291602
wikitext
text/x-wiki
The process for generating the numbered colors in [[Wiktionary:Palette]] is outlined on this page.
==Algorithm==
The basic process is outlined as follows:
# Start from a set of base colors
# Adjust each color to meet contrast requirements
This results in a color set of 10*N colors for N base colors, with each variant of a base color identified by an index 0 through 9. 0 is the closest to the background color and 9 the furthest from the background color.
===Base colors===
{| class="wikitable"
|+Overview of basic color names and hex codes
!scope="col"| Color name
!scope="col"| Hex value
!scope="col"| Example
|-
!scope="row"| red
| <code>#e63949</code> || style="background-color:#e63949; min-width: 5em;" |
|-
!scope="row"| scarlet
| <code>#e0562d</code> || style="background-color:#e0562d; min-width: 5em;" |
|-
!scope="row"| orange
| <code>#d9760f</code> || style="background-color:#d9760f; min-width: 5em;" |
|-
!scope="row"| amber
| <code>#cf9211</code> || style="background-color:#cf9211; min-width: 5em;" |
|-
!scope="row"| yellow
| <code>#baa72f</code> || style="background-color:#baa72f; min-width: 5em;" |
|-
!scope="row"| lime
| <code>#97b53c</code> || style="background-color:#97b53c; min-width: 5em;" |
|-
!scope="row"| green
| <code>#60b334</code> || style="background-color:#60b334; min-width: 5em;" |
|-
!scope="row"| teal
| <code>#32b378</code> || style="background-color:#32b378; min-width: 5em;" |
|-
!scope="row"| cyan
| <code>#28a0b5</code> || style="background-color:#28a0b5; min-width: 5em;" |
|-
!scope="row"| blue
| <code>#3271f0</code> || style="background-color:#3271f0; min-width: 5em;" |
|-
!scope="row"| indigo
| <code>#584ee6</code> || style="background-color:#584ee6; min-width: 5em;" |
|-
!scope="row"| purple
| <code>#7f38e8</code> || style="background-color:#7f38e8; min-width: 5em;" |
|-
!scope="row"| magenta
| <code>#9d2cdb</code> || style="background-color:#9d2cdb; min-width: 5em;" |
|-
!scope="row"| rose
| <code>#a6448c</code> || style="background-color:#a6448c; min-width: 5em;" |
|-
!scope="row"| brown
| <code>#856142</code> || style="background-color:#856142; min-width: 5em;" |
|-
!scope="row"| grey
| <code>#666666</code> || style="background-color:#666666; min-width: 5em;" |
|}
===Grey colors===
For each base color except grey, a new grey- variant (e.g. red → grey-red) is created by desaturating the base color by 50%.
===Variants===
Variants are derived from the base color by using a value called an ''offset'' <math>\in [-1, 1]</math>
If not otherwise specified, colors are expressed as <math>r, g, b</math> where each value <math>\in [0, 1]</math>.
To generate a variant, we use an operation <math>a</math> (<code>adjust</code>) below, which takes the base color <math>r, g, b</math> and the offset <math>o</math>:
<math display="block">a(r, g, b, o) = \begin{cases}
a_+(r, g, b, o), & \text{if } o > 0 \\
a_-(r, g, b, -o), & \text{if } o < 0 \\
{[r, g, b]}, & \text{if } o = 0 .
\end{cases}</math>
The operation <math>a_+</math> (<code>lighten</code>) lightens a color by a certain factor <math>p \in [0, 1]</math>:
<math display="block">a_+(r, g, b, p) = [(1-p)r+p,(1-p)g+p,(1-p)b+p]</math>
The operation <math>a_-</math> (<code>deepen</code>) ''deepens'' a color by a certain factor <math>p \in [0, 1]</math>. It is implemented by converting the color from RGB to HSV (with <math>s, v \in [0, 1]</math>; <math>h</math> is not altered and thus its range is insignificant), applying the operation <math>a_{-hsv}</math> to the HSV values and the factor <math>p</math>, and then converting back to RGB, where:
<math display="block">a_{-hsv}(h, s, v, p) = [h, \sqrt{s} (1 - (1 - s)(1 - p)), (1 - p)v]</math>
===Contrast requirements===
The contrast requirements apply with the variant used as the background color and the default text color, either black or white, as the foreground color.
In light mode, the text color is the default black text color, <code>#202122</code>. In dark mode, it is the default white text color, <code>#eaecf0</code>.
Both of these requirements apply, and whichever is stricter will take priority. In light mode, the contrast is against the black foreground color, and in night mode, against the white foreground color:
{| class="wikitable"
! Index !! WCAG !! APCA !! Notes
|-
| 0 || 15.00 || +100 (light), −94.5 dark) ||
|-
| 1 || 13.25 || +95 (light), −93 (dark) ||
|-
| 2 || 11.50 || ±90 || APCA Bronze Simple: body text preferred contrast is ≥+90 or ≤−90
|-
| 3 || 9.25 || ±85 ||
|-
| 4 || 7.50 || ±75 || WCAG: AAA for body text. APCA Bronze Simple: body text minimum contrast is ≥+75 or ≤−75
|-
| 5 || 6.00 || ±67.5 ||
|-
| 6 || 4.50 || ±60 || WCAG: AA for body text. APCA Bronze Simple: minimum requirement for content text is ≥+60 or ≤−60
|-
| 7 || 3.00 || ±52.5 ||
|-
| 8 || 2.10 || ±45 ||
|-
| 9 || 1.40 || ±30 || APCA Bronze Simple: absolute minimum requirement for any kind of text is ≥+30 or ≤−30
|}
Note that the requirements regarding APCA contrast for indices 0 and 1 in dark mode are different from those in light mode, since the contrast of the default foreground against the default background in the default dark color scheme is only approximately −94.7.
The color variants, and therefore offsets, that are tried are all those that result in distinct colors when expressed as 6-digit hexadecimal colors. Then, in light mode, we pick the minimum offset for which the quantized color (as if converted to 6-digit hex and back) meets both contrast requirements, and in dark mode, we pick the maximum offset for which the quantized color meets both contrast requirements. This ensures that the color is dark as possible in light mode, and as light as possible in dark mode.
==Implementation==
The below is a Python reference implementation of this algorithm.
<syntaxhighlight lang="python">
import colorsys
import re
IDENTIFICATION = "[[Wiktionary:Palette/numbered]]"
HEX_COLOR_RGB = re.compile(r"^#[0-9A-Fa-f]{6}$")
def _hex_to_rgb_comp(x):
return int(x, 16) / 255.0
def hex_to_rgb(hex_color):
"""
Converts a color in hex format into the internal format.
(R, G, B, with each value lying within [0, 1]).
"""
if not HEX_COLOR_RGB.match(hex_color):
raise ValueError("hex color format not supported")
r, g, b = hex_color[1:3], hex_color[3:5], hex_color[5:7]
return (_hex_to_rgb_comp(r), _hex_to_rgb_comp(g), _hex_to_rgb_comp(b))
def rgb_to_hex(c):
"""Converts a color into hex format."""
r, g, b = c
r = int(round(r * 255.0))
g = int(round(g * 255.0))
b = int(round(b * 255.0))
return "#{:02x}{:02x}{:02x}".format(r, g, b)
def quantize(color):
"""Quantizes a color so that it corresponds exactly to a 6-digit hex color."""
r, g, b = color
r = int(round(r * 255)) / 255
g = int(round(g * 255)) / 255
b = int(round(b * 255)) / 255
return r, g, b
def _wcag_to_srgb(x):
if x <= 0.03928:
return x / 12.2
else:
return ((x + 0.055) / 1.055) ** 2.4
def wcag_luminance(color):
"""Computes the sRGB luminance of a color according to WCAG."""
rs, gs, bs = color
r = _wcag_to_srgb(rs)
g = _wcag_to_srgb(gs)
b = _wcag_to_srgb(bs)
return 0.2126 * r + 0.7152 * g + 0.0722 * b
def wcag_contrast(fgcolor, bgcolor):
"""Computes the WCAG contrast ratio between two colors."""
fgluma = wcag_luminance(fgcolor)
bgluma = wcag_luminance(bgcolor)
ratio = (fgluma + 0.05) / (bgluma + 0.05)
if ratio < 1:
ratio = 1.0 / ratio
return ratio
def _simple_srgb_luma(color):
# <https://github.com/Myndex/SAPC-APCA/blob/master/documentation/APCA-W3-LaTeX.md>
# <https://en.wiktionary.org/w/index.php?title=Module:palette&oldid=82494940#L-57--L-94>
r, g, b = color
s_rco, s_gco, s_bco = 0.2126729, 0.7151522, 0.0721750
main_trc = 2.4
return clamp(0, s_rco * (r ** main_trc) + s_gco * (g ** main_trc) + s_bco * (b ** main_trc), 1)
def _apca_f_sc(y_c):
# <https://github.com/Myndex/SAPC-APCA/blob/master/documentation/APCA-W3-LaTeX.md>
# <https://en.wiktionary.org/w/index.php?title=Module:palette&oldid=82494940#L-57--L-94>
if y_c <= 0:
return 0
elif y_c <= 0.022:
return y_c + (0.022 - y_c) ** 1.414
else:
return y_c
def apca_contrast(fgcolor, bgcolor):
"""Computes the APCA contrast value between a foreground and background color."""
# <https://github.com/Myndex/SAPC-APCA/blob/master/documentation/APCA-W3-LaTeX.md>
# <https://en.wiktionary.org/w/index.php?title=Module:palette&oldid=82494940#L-57--L-94>
ys_fg = _simple_srgb_luma(fgcolor)
ys_bg = _simple_srgb_luma(bgcolor)
y_fg = _apca_f_sc(ys_fg)
y_bg = _apca_f_sc(ys_bg)
if y_bg > y_fg:
s_apc = (y_bg ** 0.56 - y_fg ** 0.57) * 1.14
else:
s_apc = (y_bg ** 0.65 - y_fg ** 0.62) * 1.14
if abs(s_apc) < 0.1:
l_c = 0
elif s_apc > 0:
l_c = (s_apc - 0.027) * 100
else:
l_c = (s_apc + 0.027) * 100
return l_c
def clamp(minimum, x, maximum):
"""Forces the value x between the endpoints minimum and maximum."""
assert minimum <= maximum
return max(minimum, min(maximum, x))
def _lighten_comp(component, power):
if not 0 <= power <= 1:
raise ValueError("power must be within [0, 1]")
return power + component * (1 - power)
def lighten(color, power):
"""Lightens a color by a certain power [0, 1]."""
if not 0 <= power <= 1:
raise ValueError("power must be within [0, 1]")
r, g, b = color
return (_lighten_comp(r, power), _lighten_comp(g, power), _lighten_comp(b, power))
def deepen(color, power):
"""Deepens a color by a certain power [0, 1]."""
if not 0 <= power <= 1:
raise ValueError("power must be within [0, 1]")
r, g, b = color
h, s, v = colorsys.rgb_to_hsv(r, g, b)
s_new = (1 - (1 - s) * (1 - power)) * (s ** 0.5)
v_new = v * (1 - power)
return colorsys.hsv_to_rgb(h, s_new, v_new)
def adjust(color, offset):
"""
Adjusts a color according to the offset.
If the offset is positive, the color is lightened.
If the offset is negative, the color is deepened.
"""
if offset > 0:
return lighten(color, offset)
elif offset < 0:
return deepen(color, -offset)
return color
def saturate(color, multiplier):
r, g, b = color
h, s, v = colorsys.rgb_to_hsv(r, g, b)
s_new = min(1, s * multiplier)
return colorsys.hsv_to_rgb(h, s_new, v)
class ContrastedColorMaker:
"""
Generates variants of the base color that meets contrast requirements
as a background color against the specified reference foreground color.
"""
def __init__(self, base_color, reference, night_mode):
self._night_mode = night_mode
# generate all possible colors
div = 1024
options = {}
self._base_color = base_color
self._colors = {}
self._wcag = []
self._apca = []
for i in range(-div, div + 1):
offset = clamp(-1, i / div, 1)
color = quantize(adjust(base_color, offset))
if color not in options:
options[color] = offset
self._colors[offset] = color
self._wcag.append((wcag_contrast(reference, color), offset))
self._apca.append((abs(apca_contrast(reference, color)), offset))
self._wcag.sort()
self._apca.sort()
def for_contrast(self, contrast_wcag, contrast_apca):
"""
Returns a variant of the color that meets the WCAG and APCA
requirements (the latter is optional). If no such color exists,
returns None.
"""
wcag_meets = set(offset for contrast, offset in self._wcag if contrast >= contrast_wcag)
if contrast_apca is None:
meets = wcag_meets
else:
apca_meets = set(offset for contrast, offset in self._apca if contrast >= contrast_apca)
meets = wcag_meets & apca_meets
if meets:
return self._colors[(max if self._night_mode else min)(meets)]
return None
BASE = {
"red": hex_to_rgb("#e63949"),
"scarlet": hex_to_rgb("#e0562d"),
"orange": hex_to_rgb("#d9760f"),
"amber": hex_to_rgb("#cf9211"),
"yellow": hex_to_rgb("#baa72f"),
"lime": hex_to_rgb("#97b53c"),
"green": hex_to_rgb("#60b334"),
"teal": hex_to_rgb("#32b378"),
"cyan": hex_to_rgb("#28a0b5"),
"blue": hex_to_rgb("#3271f0"),
"indigo": hex_to_rgb("#584ee6"),
"purple": hex_to_rgb("#7f38e8"),
"magenta": hex_to_rgb("#9d2cdb"),
"rose": hex_to_rgb("#a6448c"),
"brown": hex_to_rgb("#856142"),
}
WHITE_BG = "#ffffff"
BLACK_FG = "#202122"
WHITE_FG = "#eaecf0"
BLACK_BG = "#101418"
white = hex_to_rgb(WHITE_FG)
black = hex_to_rgb(BLACK_FG)
CONTRASTS = {
0: (15.00, 100, 94.5),
1: (13.25, 95, 93),
2: (11.50, 90, 90),
3: ( 9.25, 85, 85),
4: ( 7.50, 75, 75),
5: ( 6.00, 67.5, 67.5),
6: ( 4.50, 60, 60),
7: ( 3.00, 52.5, 52.5),
8: ( 2.10, 45, 45),
9: ( 1.40, 30, 30),
}
# generate -grey- variants.
for key, value in list(BASE.items()):
BASE["grey-" + key] = saturate(value, 0.5)
BASE["grey"] = hex_to_rgb("#666666")
colors_light = {}
colors_night = {}
for base_color_name, base_color in BASE.items():
tints_light = colors_light[base_color_name] = {}
tints_night = colors_night[base_color_name] = {}
contrasted_color_maker_light = ContrastedColorMaker(base_color, black, False)
contrasted_color_maker_night = ContrastedColorMaker(base_color, white, True)
for tint_number, (contrast_wcag, contrast_apca, contrast_apca_night) in CONTRASTS.items():
tint_light = contrasted_color_maker_light.for_contrast(contrast_wcag, contrast_apca)
if tint_light is None:
raise ValueError(f"light mode contrast requirement cannot be met (WCAG: {contrast_wcag}, APCA: {contrast_apca}, base color: {rgb_to_hex(base_color)}, foreground color: {rgb_to_hex(black)})")
tint_night = contrasted_color_maker_night.for_contrast(contrast_wcag, contrast_apca_night)
if tint_night is None:
raise ValueError(f"night mode contrast requirement cannot be met (WCAG: {contrast_wcag}, APCA: {-contrast_apca_night}, base color: {rgb_to_hex(base_color)}, foreground color: {rgb_to_hex(white)})")
tints_light[tint_number] = tint_light
tints_night[tint_number] = tint_night
def make_swatch_bg(colors, fg_color):
lines = []
for base_color_name, tints in colors.items():
if lines:
lines.append('|-')
else:
lines.append('{|')
for tint_number, color in tints.items():
name = f"{base_color_name}-{tint_number}"
lines.append(f'| style="background:{rgb_to_hex(color)}" | <div style="color:{fg_color};padding:15px 5px">{name}</div>')
lines.append("|}")
return "\n".join(lines)
def make_swatch_fg(colors, bg_color):
lines = []
for base_color_name, tints in colors.items():
if lines:
lines.append('|-')
else:
lines.append("{| " + f'style="background:{bg_color}"')
for tint_number, color in tints.items():
name = f"{base_color_name}-{tint_number}"
lines.append(f'| <div style="color:{rgb_to_hex(color)};padding:5px">{name}</div>')
lines.append("|}")
return "\n".join(lines)
def make_unified_swatch_bg(colors):
lines = []
for base_color_name, tints in colors.items():
if lines:
lines.append('|-')
else:
lines.append("{| " + f'class="wikt-auto-contrast-exempt" style="color:var(--color-base)"')
for tint_number, color in tints.items():
name = f"{base_color_name}-{tint_number}"
lines.append(f'| style="background-color:var(--wikt-palette-{name},{rgb_to_hex(color)})" | <div style="padding:15px 5px">{name}</div>')
lines.append("|}")
return "\n".join(lines)
def make_unified_swatch_fg(colors):
lines = []
for base_color_name, tints in colors.items():
if lines:
lines.append('|-')
else:
lines.append("{| " + f'class="wikt-auto-contrast-exempt" style="background:var(--background-color-base)"')
for tint_number, color in tints.items():
name = f"{base_color_name}-{tint_number}"
lines.append(f'| <div style="color:var(--wikt-palette-{name},{rgb_to_hex(color)});padding:5px">{name}</div>')
lines.append("|}")
return "\n".join(lines)
def make_palette(colors_light, colors_night):
def wrap_frame(prefix, indent, suffix):
def _wrap(lines):
return prefix + "\n" + "\n".join(indent + line for line in lines) + "\n" + suffix
return _wrap
def gather_colors(colors):
rules = ["/* Autogenerated with " + IDENTIFICATION + " */"]
for base_color_name, tints in colors.items():
for tint_number, color in tints.items():
name = f"{base_color_name}-{tint_number}"
rules.append(f"--wikt-palette-{name}: {rgb_to_hex(color)};")
return rules
light_block = wrap_frame(":root, .skin-invert, .notheme {", "\t", "}")
night_block_1 = wrap_frame("@media screen {\n\thtml.skin-theme-clientpref-night {", "\t\t", "\t}\n}")
night_block_2 = wrap_frame("@media screen and (prefers-color-scheme: dark) {\n\thtml.skin-theme-clientpref-os {".strip(), "\t\t", "\t}\n}")
rules_light = gather_colors(colors_light)
rules_night = gather_colors(colors_night)
return light_block(rules_light) + "\n\n/* Styles need to be duplicated exactly between these two selectors. */\n" + night_block_1(rules_night) + "\n" + night_block_2(rules_night)
def print_swatches():
print("==Swatches==")
print("")
print("===Light mode===")
print("<!-- Autogenerated with " + IDENTIFICATION + " */ -->")
print(make_swatch_bg(colors_light, BLACK_FG))
print("")
print(make_swatch_fg(colors_light, WHITE_BG))
print("")
print("===Night mode===")
print("<!-- Autogenerated with " + IDENTIFICATION + " */ -->")
print(make_swatch_bg(colors_night, WHITE_FG))
print("")
print(make_swatch_fg(colors_night, BLACK_BG))
def print_unified_swatch():
print("==Swatch==")
print("Automatically adjusts for light/dark mode.")
print("<!-- Autogenerated with " + IDENTIFICATION + " */ -->")
print(make_unified_swatch_bg(colors_light))
print("")
print(make_unified_swatch_fg(colors_light))
def print_palette():
print(make_palette(colors_light, colors_night))
if __name__ == "__main__":
#print_swatches()
print_unified_swatch()
#print_palette()
</syntaxhighlight>
==Swatch==
Automatically adjusts for light/dark mode.
<!-- Autogenerated with [[Wiktionary:Palette/numbered]] */ -->
{| class="wikt-auto-contrast-exempt" style="color:var(--color-base)"
| style="background-color:var(--wikt-palette-red-0,#fef9f9)" | <div style="padding:15px 5px">red-0</div>
| style="background-color:var(--wikt-palette-red-1,#fdeef0)" | <div style="padding:15px 5px">red-1</div>
| style="background-color:var(--wikt-palette-red-2,#fce4e6)" | <div style="padding:15px 5px">red-2</div>
| style="background-color:var(--wikt-palette-red-3,#fad9dc)" | <div style="padding:15px 5px">red-3</div>
| style="background-color:var(--wikt-palette-red-4,#f7c2c6)" | <div style="padding:15px 5px">red-4</div>
| style="background-color:var(--wikt-palette-red-5,#f5afb5)" | <div style="padding:15px 5px">red-5</div>
| style="background-color:var(--wikt-palette-red-6,#f29ba3)" | <div style="padding:15px 5px">red-6</div>
| style="background-color:var(--wikt-palette-red-7,#f0858e)" | <div style="padding:15px 5px">red-7</div>
| style="background-color:var(--wikt-palette-red-8,#ed6d78)" | <div style="padding:15px 5px">red-8</div>
| style="background-color:var(--wikt-palette-red-9,#ce4350)" | <div style="padding:15px 5px">red-9</div>
|-
| style="background-color:var(--wikt-palette-scarlet-0,#fef9f7)" | <div style="padding:15px 5px">scarlet-0</div>
| style="background-color:var(--wikt-palette-scarlet-1,#fcefeb)" | <div style="padding:15px 5px">scarlet-1</div>
| style="background-color:var(--wikt-palette-scarlet-2,#fae5de)" | <div style="padding:15px 5px">scarlet-2</div>
| style="background-color:var(--wikt-palette-scarlet-3,#f8dad1)" | <div style="padding:15px 5px">scarlet-3</div>
| style="background-color:var(--wikt-palette-scarlet-4,#f4c4b5)" | <div style="padding:15px 5px">scarlet-4</div>
| style="background-color:var(--wikt-palette-scarlet-5,#f1b3a0)" | <div style="padding:15px 5px">scarlet-5</div>
| style="background-color:var(--wikt-palette-scarlet-6,#eea088)" | <div style="padding:15px 5px">scarlet-6</div>
| style="background-color:var(--wikt-palette-scarlet-7,#ea8c70)" | <div style="padding:15px 5px">scarlet-7</div>
| style="background-color:var(--wikt-palette-scarlet-8,#e67553)" | <div style="padding:15px 5px">scarlet-8</div>
| style="background-color:var(--wikt-palette-scarlet-9,#c25433)" | <div style="padding:15px 5px">scarlet-9</div>
|-
| style="background-color:var(--wikt-palette-orange-0,#fdfaf5)" | <div style="padding:15px 5px">orange-0</div>
| style="background-color:var(--wikt-palette-orange-1,#fbf0e4)" | <div style="padding:15px 5px">orange-1</div>
| style="background-color:var(--wikt-palette-orange-2,#f8e7d4)" | <div style="padding:15px 5px">orange-2</div>
| style="background-color:var(--wikt-palette-orange-3,#f5ddc3)" | <div style="padding:15px 5px">orange-3</div>
| style="background-color:var(--wikt-palette-orange-4,#f0c89e)" | <div style="padding:15px 5px">orange-4</div>
| style="background-color:var(--wikt-palette-orange-5,#ebb882)" | <div style="padding:15px 5px">orange-5</div>
| style="background-color:var(--wikt-palette-orange-6,#e6a662)" | <div style="padding:15px 5px">orange-6</div>
| style="background-color:var(--wikt-palette-orange-7,#e19341)" | <div style="padding:15px 5px">orange-7</div>
| style="background-color:var(--wikt-palette-orange-8,#db7f1e)" | <div style="padding:15px 5px">orange-8</div>
| style="background-color:var(--wikt-palette-orange-9,#b16210)" | <div style="padding:15px 5px">orange-9</div>
|-
| style="background-color:var(--wikt-palette-amber-0,#fdfaf3)" | <div style="padding:15px 5px">amber-0</div>
| style="background-color:var(--wikt-palette-amber-1,#f9f1df)" | <div style="padding:15px 5px">amber-1</div>
| style="background-color:var(--wikt-palette-amber-2,#f5e8cc)" | <div style="padding:15px 5px">amber-2</div>
| style="background-color:var(--wikt-palette-amber-3,#f1dfb8)" | <div style="padding:15px 5px">amber-3</div>
| style="background-color:var(--wikt-palette-amber-4,#e8cc8f)" | <div style="padding:15px 5px">amber-4</div>
| style="background-color:var(--wikt-palette-amber-5,#e2bc6d)" | <div style="padding:15px 5px">amber-5</div>
| style="background-color:var(--wikt-palette-amber-6,#dbac4a)" | <div style="padding:15px 5px">amber-6</div>
| style="background-color:var(--wikt-palette-amber-7,#d39b24)" | <div style="padding:15px 5px">amber-7</div>
| style="background-color:var(--wikt-palette-amber-8,#c38c17)" | <div style="padding:15px 5px">amber-8</div>
| style="background-color:var(--wikt-palette-amber-9,#9b6f10)" | <div style="padding:15px 5px">amber-9</div>
|-
| style="background-color:var(--wikt-palette-yellow-0,#fbfbf4)" | <div style="padding:15px 5px">yellow-0</div>
| style="background-color:var(--wikt-palette-yellow-1,#f5f2e0)" | <div style="padding:15px 5px">yellow-1</div>
| style="background-color:var(--wikt-palette-yellow-2,#eeeacc)" | <div style="padding:15px 5px">yellow-2</div>
| style="background-color:var(--wikt-palette-yellow-3,#e8e2b9)" | <div style="padding:15px 5px">yellow-3</div>
| style="background-color:var(--wikt-palette-yellow-4,#dbd191)" | <div style="padding:15px 5px">yellow-4</div>
| style="background-color:var(--wikt-palette-yellow-5,#d0c370)" | <div style="padding:15px 5px">yellow-5</div>
| style="background-color:var(--wikt-palette-yellow-6,#c5b44f)" | <div style="padding:15px 5px">yellow-6</div>
| style="background-color:var(--wikt-palette-yellow-7,#b6a63f)" | <div style="padding:15px 5px">yellow-7</div>
| style="background-color:var(--wikt-palette-yellow-8,#a69737)" | <div style="padding:15px 5px">yellow-8</div>
| style="background-color:var(--wikt-palette-yellow-9,#847827)" | <div style="padding:15px 5px">yellow-9</div>
|-
| style="background-color:var(--wikt-palette-lime-0,#f9fbf3)" | <div style="padding:15px 5px">lime-0</div>
| style="background-color:var(--wikt-palette-lime-1,#eff4e1)" | <div style="padding:15px 5px">lime-1</div>
| style="background-color:var(--wikt-palette-lime-2,#e5edce)" | <div style="padding:15px 5px">lime-2</div>
| style="background-color:var(--wikt-palette-lime-3,#dbe5bc)" | <div style="padding:15px 5px">lime-3</div>
| style="background-color:var(--wikt-palette-lime-4,#c5d693)" | <div style="padding:15px 5px">lime-4</div>
| style="background-color:var(--wikt-palette-lime-5,#b5ca73)" | <div style="padding:15px 5px">lime-5</div>
| style="background-color:var(--wikt-palette-lime-6,#a3be52)" | <div style="padding:15px 5px">lime-6</div>
| style="background-color:var(--wikt-palette-lime-7,#97af4e)" | <div style="padding:15px 5px">lime-7</div>
| style="background-color:var(--wikt-palette-lime-8,#889f43)" | <div style="padding:15px 5px">lime-8</div>
| style="background-color:var(--wikt-palette-lime-9,#6b7f2f)" | <div style="padding:15px 5px">lime-9</div>
|-
| style="background-color:var(--wikt-palette-green-0,#f8fbf5)" | <div style="padding:15px 5px">green-0</div>
| style="background-color:var(--wikt-palette-green-1,#eaf5e4)" | <div style="padding:15px 5px">green-1</div>
| style="background-color:var(--wikt-palette-green-2,#ddefd3)" | <div style="padding:15px 5px">green-2</div>
| style="background-color:var(--wikt-palette-green-3,#cee8c1)" | <div style="padding:15px 5px">green-3</div>
| style="background-color:var(--wikt-palette-green-4,#b3da9d)" | <div style="padding:15px 5px">green-4</div>
| style="background-color:var(--wikt-palette-green-5,#9cd080)" | <div style="padding:15px 5px">green-5</div>
| style="background-color:var(--wikt-palette-green-6,#83c460)" | <div style="padding:15px 5px">green-6</div>
| style="background-color:var(--wikt-palette-green-7,#69b840)" | <div style="padding:15px 5px">green-7</div>
| style="background-color:var(--wikt-palette-green-8,#64a640)" | <div style="padding:15px 5px">green-8</div>
| style="background-color:var(--wikt-palette-green-9,#4b852d)" | <div style="padding:15px 5px">green-9</div>
|-
| style="background-color:var(--wikt-palette-teal-0,#f6fcf9)" | <div style="padding:15px 5px">teal-0</div>
| style="background-color:var(--wikt-palette-teal-1,#e5f6ee)" | <div style="padding:15px 5px">teal-1</div>
| style="background-color:var(--wikt-palette-teal-2,#d5efe3)" | <div style="padding:15px 5px">teal-2</div>
| style="background-color:var(--wikt-palette-teal-3,#c3e9d7)" | <div style="padding:15px 5px">teal-3</div>
| style="background-color:var(--wikt-palette-teal-4,#9fdcc0)" | <div style="padding:15px 5px">teal-4</div>
| style="background-color:var(--wikt-palette-teal-5,#82d1ac)" | <div style="padding:15px 5px">teal-5</div>
| style="background-color:var(--wikt-palette-teal-6,#64c599)" | <div style="padding:15px 5px">teal-6</div>
| style="background-color:var(--wikt-palette-teal-7,#41b982)" | <div style="padding:15px 5px">teal-7</div>
| style="background-color:var(--wikt-palette-teal-8,#3fa878)" | <div style="padding:15px 5px">teal-8</div>
| style="background-color:var(--wikt-palette-teal-9,#2c865d)" | <div style="padding:15px 5px">teal-9</div>
|-
| style="background-color:var(--wikt-palette-cyan-0,#f6fbfc)" | <div style="padding:15px 5px">cyan-0</div>
| style="background-color:var(--wikt-palette-cyan-1,#e7f4f7)" | <div style="padding:15px 5px">cyan-1</div>
| style="background-color:var(--wikt-palette-cyan-2,#d7edf1)" | <div style="padding:15px 5px">cyan-2</div>
| style="background-color:var(--wikt-palette-cyan-3,#c7e6ec)" | <div style="padding:15px 5px">cyan-3</div>
| style="background-color:var(--wikt-palette-cyan-4,#a6d7e0)" | <div style="padding:15px 5px">cyan-4</div>
| style="background-color:var(--wikt-palette-cyan-5,#8accd7)" | <div style="padding:15px 5px">cyan-5</div>
| style="background-color:var(--wikt-palette-cyan-6,#6ebfcd)" | <div style="padding:15px 5px">cyan-6</div>
| style="background-color:var(--wikt-palette-cyan-7,#50b2c3)" | <div style="padding:15px 5px">cyan-7</div>
| style="background-color:var(--wikt-palette-cyan-8,#2ea3b7)" | <div style="padding:15px 5px">cyan-8</div>
| style="background-color:var(--wikt-palette-cyan-9,#288191)" | <div style="padding:15px 5px">cyan-9</div>
|-
| style="background-color:var(--wikt-palette-blue-0,#f8fafe)" | <div style="padding:15px 5px">blue-0</div>
| style="background-color:var(--wikt-palette-blue-1,#ecf2fe)" | <div style="padding:15px 5px">blue-1</div>
| style="background-color:var(--wikt-palette-blue-2,#e0eafd)" | <div style="padding:15px 5px">blue-2</div>
| style="background-color:var(--wikt-palette-blue-3,#d3e1fc)" | <div style="padding:15px 5px">blue-3</div>
| style="background-color:var(--wikt-palette-blue-4,#bbd0fa)" | <div style="padding:15px 5px">blue-4</div>
| style="background-color:var(--wikt-palette-blue-5,#a6c2f9)" | <div style="padding:15px 5px">blue-5</div>
| style="background-color:var(--wikt-palette-blue-6,#92b3f7)" | <div style="padding:15px 5px">blue-6</div>
| style="background-color:var(--wikt-palette-blue-7,#7ca4f5)" | <div style="padding:15px 5px">blue-7</div>
| style="background-color:var(--wikt-palette-blue-8,#6494f4)" | <div style="padding:15px 5px">blue-8</div>
| style="background-color:var(--wikt-palette-blue-9,#3d72db)" | <div style="padding:15px 5px">blue-9</div>
|-
| style="background-color:var(--wikt-palette-indigo-0,#fafafe)" | <div style="padding:15px 5px">indigo-0</div>
| style="background-color:var(--wikt-palette-indigo-1,#f1f1fd)" | <div style="padding:15px 5px">indigo-1</div>
| style="background-color:var(--wikt-palette-indigo-2,#e9e7fc)" | <div style="padding:15px 5px">indigo-2</div>
| style="background-color:var(--wikt-palette-indigo-3,#e0defa)" | <div style="padding:15px 5px">indigo-3</div>
| style="background-color:var(--wikt-palette-indigo-4,#cecbf8)" | <div style="padding:15px 5px">indigo-4</div>
| style="background-color:var(--wikt-palette-indigo-5,#bfbcf5)" | <div style="padding:15px 5px">indigo-5</div>
| style="background-color:var(--wikt-palette-indigo-6,#b0acf3)" | <div style="padding:15px 5px">indigo-6</div>
| style="background-color:var(--wikt-palette-indigo-7,#a19cf1)" | <div style="padding:15px 5px">indigo-7</div>
| style="background-color:var(--wikt-palette-indigo-8,#918aef)" | <div style="padding:15px 5px">indigo-8</div>
| style="background-color:var(--wikt-palette-indigo-9,#6e66e0)" | <div style="padding:15px 5px">indigo-9</div>
|-
| style="background-color:var(--wikt-palette-purple-0,#fcfafe)" | <div style="padding:15px 5px">purple-0</div>
| style="background-color:var(--wikt-palette-purple-1,#f5f0fd)" | <div style="padding:15px 5px">purple-1</div>
| style="background-color:var(--wikt-palette-purple-2,#efe6fc)" | <div style="padding:15px 5px">purple-2</div>
| style="background-color:var(--wikt-palette-purple-3,#e8dbfb)" | <div style="padding:15px 5px">purple-3</div>
| style="background-color:var(--wikt-palette-purple-4,#dbc6f8)" | <div style="padding:15px 5px">purple-4</div>
| style="background-color:var(--wikt-palette-purple-5,#d0b6f7)" | <div style="padding:15px 5px">purple-5</div>
| style="background-color:var(--wikt-palette-purple-6,#c5a5f5)" | <div style="padding:15px 5px">purple-6</div>
| style="background-color:var(--wikt-palette-purple-7,#b993f2)" | <div style="padding:15px 5px">purple-7</div>
| style="background-color:var(--wikt-palette-purple-8,#ad7ff0)" | <div style="padding:15px 5px">purple-8</div>
| style="background-color:var(--wikt-palette-purple-9,#9053eb)" | <div style="padding:15px 5px">purple-9</div>
|-
| style="background-color:var(--wikt-palette-magenta-0,#fcf9fe)" | <div style="padding:15px 5px">magenta-0</div>
| style="background-color:var(--wikt-palette-magenta-1,#f7effc)" | <div style="padding:15px 5px">magenta-1</div>
| style="background-color:var(--wikt-palette-magenta-2,#f3e4fa)" | <div style="padding:15px 5px">magenta-2</div>
| style="background-color:var(--wikt-palette-magenta-3,#eedaf9)" | <div style="padding:15px 5px">magenta-3</div>
| style="background-color:var(--wikt-palette-magenta-4,#e3c4f5)" | <div style="padding:15px 5px">magenta-4</div>
| style="background-color:var(--wikt-palette-magenta-5,#dbb2f2)" | <div style="padding:15px 5px">magenta-5</div>
| style="background-color:var(--wikt-palette-magenta-6,#d3a0ef)" | <div style="padding:15px 5px">magenta-6</div>
| style="background-color:var(--wikt-palette-magenta-7,#ca8deb)" | <div style="padding:15px 5px">magenta-7</div>
| style="background-color:var(--wikt-palette-magenta-8,#c078e8)" | <div style="padding:15px 5px">magenta-8</div>
| style="background-color:var(--wikt-palette-magenta-9,#a844df)" | <div style="padding:15px 5px">magenta-9</div>
|-
| style="background-color:var(--wikt-palette-rose-0,#fcf9fc)" | <div style="padding:15px 5px">rose-0</div>
| style="background-color:var(--wikt-palette-rose-1,#f8f0f6)" | <div style="padding:15px 5px">rose-1</div>
| style="background-color:var(--wikt-palette-rose-2,#f3e6ef)" | <div style="padding:15px 5px">rose-2</div>
| style="background-color:var(--wikt-palette-rose-3,#eedbe9)" | <div style="padding:15px 5px">rose-3</div>
| style="background-color:var(--wikt-palette-rose-4,#e4c7dc)" | <div style="padding:15px 5px">rose-4</div>
| style="background-color:var(--wikt-palette-rose-5,#dcb6d2)" | <div style="padding:15px 5px">rose-5</div>
| style="background-color:var(--wikt-palette-rose-6,#d4a5c8)" | <div style="padding:15px 5px">rose-6</div>
| style="background-color:var(--wikt-palette-rose-7,#cc94bd)" | <div style="padding:15px 5px">rose-7</div>
| style="background-color:var(--wikt-palette-rose-8,#c381b1)" | <div style="padding:15px 5px">rose-8</div>
| style="background-color:var(--wikt-palette-rose-9,#af5797)" | <div style="padding:15px 5px">rose-9</div>
|-
| style="background-color:var(--wikt-palette-brown-0,#fbfaf9)" | <div style="padding:15px 5px">brown-0</div>
| style="background-color:var(--wikt-palette-brown-1,#f4f1ee)" | <div style="padding:15px 5px">brown-1</div>
| style="background-color:var(--wikt-palette-brown-2,#eee8e4)" | <div style="padding:15px 5px">brown-2</div>
| style="background-color:var(--wikt-palette-brown-3,#e7dfd9)" | <div style="padding:15px 5px">brown-3</div>
| style="background-color:var(--wikt-palette-brown-4,#d8cdc3)" | <div style="padding:15px 5px">brown-4</div>
| style="background-color:var(--wikt-palette-brown-5,#cdbfb2)" | <div style="padding:15px 5px">brown-5</div>
| style="background-color:var(--wikt-palette-brown-6,#c2b1a1)" | <div style="padding:15px 5px">brown-6</div>
| style="background-color:var(--wikt-palette-brown-7,#b7a18f)" | <div style="padding:15px 5px">brown-7</div>
| style="background-color:var(--wikt-palette-brown-8,#ab927c)" | <div style="padding:15px 5px">brown-8</div>
| style="background-color:var(--wikt-palette-brown-9,#917155)" | <div style="padding:15px 5px">brown-9</div>
|-
| style="background-color:var(--wikt-palette-grey-red-0,#fef9f9)" | <div style="padding:15px 5px">grey-red-0</div>
| style="background-color:var(--wikt-palette-grey-red-1,#fbeff0)" | <div style="padding:15px 5px">grey-red-1</div>
| style="background-color:var(--wikt-palette-grey-red-2,#f9e5e6)" | <div style="padding:15px 5px">grey-red-2</div>
| style="background-color:var(--wikt-palette-grey-red-3,#f7dadc)" | <div style="padding:15px 5px">grey-red-3</div>
| style="background-color:var(--wikt-palette-grey-red-4,#f2c3c8)" | <div style="padding:15px 5px">grey-red-4</div>
| style="background-color:var(--wikt-palette-grey-red-5,#eeb2b7)" | <div style="padding:15px 5px">grey-red-5</div>
| style="background-color:var(--wikt-palette-grey-red-6,#dca5ab)" | <div style="padding:15px 5px">grey-red-6</div>
| style="background-color:var(--wikt-palette-grey-red-7,#ce979c)" | <div style="padding:15px 5px">grey-red-7</div>
| style="background-color:var(--wikt-palette-grey-red-8,#c0878c)" | <div style="padding:15px 5px">grey-red-8</div>
| style="background-color:var(--wikt-palette-grey-red-9,#9f686d)" | <div style="padding:15px 5px">grey-red-9</div>
|-
| style="background-color:var(--wikt-palette-grey-scarlet-0,#fdfaf8)" | <div style="padding:15px 5px">grey-scarlet-0</div>
| style="background-color:var(--wikt-palette-grey-scarlet-1,#faf0ec)" | <div style="padding:15px 5px">grey-scarlet-1</div>
| style="background-color:var(--wikt-palette-grey-scarlet-2,#f7e6e0)" | <div style="padding:15px 5px">grey-scarlet-2</div>
| style="background-color:var(--wikt-palette-grey-scarlet-3,#f4dcd4)" | <div style="padding:15px 5px">grey-scarlet-3</div>
| style="background-color:var(--wikt-palette-grey-scarlet-4,#eec7bb)" | <div style="padding:15px 5px">grey-scarlet-4</div>
| style="background-color:var(--wikt-palette-grey-scarlet-5,#e8b6a7)" | <div style="padding:15px 5px">grey-scarlet-5</div>
| style="background-color:var(--wikt-palette-grey-scarlet-6,#d6a99c)" | <div style="padding:15px 5px">grey-scarlet-6</div>
| style="background-color:var(--wikt-palette-grey-scarlet-7,#c89b8d)" | <div style="padding:15px 5px">grey-scarlet-7</div>
| style="background-color:var(--wikt-palette-grey-scarlet-8,#b98c7e)" | <div style="padding:15px 5px">grey-scarlet-8</div>
| style="background-color:var(--wikt-palette-grey-scarlet-9,#986d5f)" | <div style="padding:15px 5px">grey-scarlet-9</div>
|-
| style="background-color:var(--wikt-palette-grey-orange-0,#fdfaf6)" | <div style="padding:15px 5px">grey-orange-0</div>
| style="background-color:var(--wikt-palette-grey-orange-1,#f9f0e8)" | <div style="padding:15px 5px">grey-orange-1</div>
| style="background-color:var(--wikt-palette-grey-orange-2,#f4e7d9)" | <div style="padding:15px 5px">grey-orange-2</div>
| style="background-color:var(--wikt-palette-grey-orange-3,#f0deca)" | <div style="padding:15px 5px">grey-orange-3</div>
| style="background-color:var(--wikt-palette-grey-orange-4,#e8caaa)" | <div style="padding:15px 5px">grey-orange-4</div>
| style="background-color:var(--wikt-palette-grey-orange-5,#e1bb92)" | <div style="padding:15px 5px">grey-orange-5</div>
| style="background-color:var(--wikt-palette-grey-orange-6,#cfae8a)" | <div style="padding:15px 5px">grey-orange-6</div>
| style="background-color:var(--wikt-palette-grey-orange-7,#c19f7c)" | <div style="padding:15px 5px">grey-orange-7</div>
| style="background-color:var(--wikt-palette-grey-orange-8,#b2906d)" | <div style="padding:15px 5px">grey-orange-8</div>
| style="background-color:var(--wikt-palette-grey-orange-9,#917151)" | <div style="padding:15px 5px">grey-orange-9</div>
|-
| style="background-color:var(--wikt-palette-grey-amber-0,#fcfaf5)" | <div style="padding:15px 5px">grey-amber-0</div>
| style="background-color:var(--wikt-palette-grey-amber-1,#f7f1e6)" | <div style="padding:15px 5px">grey-amber-1</div>
| style="background-color:var(--wikt-palette-grey-amber-2,#f1e9d6)" | <div style="padding:15px 5px">grey-amber-2</div>
| style="background-color:var(--wikt-palette-grey-amber-3,#ece0c6)" | <div style="padding:15px 5px">grey-amber-3</div>
| style="background-color:var(--wikt-palette-grey-amber-4,#e0cda3)" | <div style="padding:15px 5px">grey-amber-4</div>
| style="background-color:var(--wikt-palette-grey-amber-5,#d8bf8a)" | <div style="padding:15px 5px">grey-amber-5</div>
| style="background-color:var(--wikt-palette-grey-amber-6,#c6b185)" | <div style="padding:15px 5px">grey-amber-6</div>
| style="background-color:var(--wikt-palette-grey-amber-7,#b7a377)" | <div style="padding:15px 5px">grey-amber-7</div>
| style="background-color:var(--wikt-palette-grey-amber-8,#a89468)" | <div style="padding:15px 5px">grey-amber-8</div>
| style="background-color:var(--wikt-palette-grey-amber-9,#88754c)" | <div style="padding:15px 5px">grey-amber-9</div>
|-
| style="background-color:var(--wikt-palette-grey-yellow-0,#fbfaf6)" | <div style="padding:15px 5px">grey-yellow-0</div>
| style="background-color:var(--wikt-palette-grey-yellow-1,#f3f2e7)" | <div style="padding:15px 5px">grey-yellow-1</div>
| style="background-color:var(--wikt-palette-grey-yellow-2,#ecead9)" | <div style="padding:15px 5px">grey-yellow-2</div>
| style="background-color:var(--wikt-palette-grey-yellow-3,#e5e1ca)" | <div style="padding:15px 5px">grey-yellow-3</div>
| style="background-color:var(--wikt-palette-grey-yellow-4,#d5d0ab)" | <div style="padding:15px 5px">grey-yellow-4</div>
| style="background-color:var(--wikt-palette-grey-yellow-5,#cac394)" | <div style="padding:15px 5px">grey-yellow-5</div>
| style="background-color:var(--wikt-palette-grey-yellow-6,#beb57c)" | <div style="padding:15px 5px">grey-yellow-6</div>
| style="background-color:var(--wikt-palette-grey-yellow-7,#aca67f)" | <div style="padding:15px 5px">grey-yellow-7</div>
| style="background-color:var(--wikt-palette-grey-yellow-8,#9e9770)" | <div style="padding:15px 5px">grey-yellow-8</div>
| style="background-color:var(--wikt-palette-grey-yellow-9,#7e7851)" | <div style="padding:15px 5px">grey-yellow-9</div>
|-
| style="background-color:var(--wikt-palette-grey-lime-0,#fafbf7)" | <div style="padding:15px 5px">grey-lime-0</div>
| style="background-color:var(--wikt-palette-grey-lime-1,#f0f3e8)" | <div style="padding:15px 5px">grey-lime-1</div>
| style="background-color:var(--wikt-palette-grey-lime-2,#e7ebda)" | <div style="padding:15px 5px">grey-lime-2</div>
| style="background-color:var(--wikt-palette-grey-lime-3,#dee3cc)" | <div style="padding:15px 5px">grey-lime-3</div>
| style="background-color:var(--wikt-palette-grey-lime-4,#cad3ae)" | <div style="padding:15px 5px">grey-lime-4</div>
| style="background-color:var(--wikt-palette-grey-lime-5,#bbc798)" | <div style="padding:15px 5px">grey-lime-5</div>
| style="background-color:var(--wikt-palette-grey-lime-6,#abba81)" | <div style="padding:15px 5px">grey-lime-6</div>
| style="background-color:var(--wikt-palette-grey-lime-7,#a0a984)" | <div style="padding:15px 5px">grey-lime-7</div>
| style="background-color:var(--wikt-palette-grey-lime-8,#919b74)" | <div style="padding:15px 5px">grey-lime-8</div>
| style="background-color:var(--wikt-palette-grey-lime-9,#727c55)" | <div style="padding:15px 5px">grey-lime-9</div>
|-
| style="background-color:var(--wikt-palette-grey-green-0,#f8fbf7)" | <div style="padding:15px 5px">grey-green-0</div>
| style="background-color:var(--wikt-palette-grey-green-1,#edf4ea)" | <div style="padding:15px 5px">grey-green-1</div>
| style="background-color:var(--wikt-palette-grey-green-2,#e2ecdd)" | <div style="padding:15px 5px">grey-green-2</div>
| style="background-color:var(--wikt-palette-grey-green-3,#d6e5ce)" | <div style="padding:15px 5px">grey-green-3</div>
| style="background-color:var(--wikt-palette-grey-green-4,#bfd6b3)" | <div style="padding:15px 5px">grey-green-4</div>
| style="background-color:var(--wikt-palette-grey-green-5,#acca9d)" | <div style="padding:15px 5px">grey-green-5</div>
| style="background-color:var(--wikt-palette-grey-green-6,#9abe87)" | <div style="padding:15px 5px">grey-green-6</div>
| style="background-color:var(--wikt-palette-grey-green-7,#93ad86)" | <div style="padding:15px 5px">grey-green-7</div>
| style="background-color:var(--wikt-palette-grey-green-8,#839e75)" | <div style="padding:15px 5px">grey-green-8</div>
| style="background-color:var(--wikt-palette-grey-green-9,#647f56)" | <div style="padding:15px 5px">grey-green-9</div>
|-
| style="background-color:var(--wikt-palette-grey-teal-0,#f7fbf9)" | <div style="padding:15px 5px">grey-teal-0</div>
| style="background-color:var(--wikt-palette-grey-teal-1,#eaf4ef)" | <div style="padding:15px 5px">grey-teal-1</div>
| style="background-color:var(--wikt-palette-grey-teal-2,#ddede5)" | <div style="padding:15px 5px">grey-teal-2</div>
| style="background-color:var(--wikt-palette-grey-teal-3,#d0e6dc)" | <div style="padding:15px 5px">grey-teal-3</div>
| style="background-color:var(--wikt-palette-grey-teal-4,#b4d7c7)" | <div style="padding:15px 5px">grey-teal-4</div>
| style="background-color:var(--wikt-palette-grey-teal-5,#9ecbb6)" | <div style="padding:15px 5px">grey-teal-5</div>
| style="background-color:var(--wikt-palette-grey-teal-6,#88bfa5)" | <div style="padding:15px 5px">grey-teal-6</div>
| style="background-color:var(--wikt-palette-grey-teal-7,#86ae9b)" | <div style="padding:15px 5px">grey-teal-7</div>
| style="background-color:var(--wikt-palette-grey-teal-8,#769f8c)" | <div style="padding:15px 5px">grey-teal-8</div>
| style="background-color:var(--wikt-palette-grey-teal-9,#56806d)" | <div style="padding:15px 5px">grey-teal-9</div>
|-
| style="background-color:var(--wikt-palette-grey-cyan-0,#f7fbfb)" | <div style="padding:15px 5px">grey-cyan-0</div>
| style="background-color:var(--wikt-palette-grey-cyan-1,#ebf3f5)" | <div style="padding:15px 5px">grey-cyan-1</div>
| style="background-color:var(--wikt-palette-grey-cyan-2,#deecee)" | <div style="padding:15px 5px">grey-cyan-2</div>
| style="background-color:var(--wikt-palette-grey-cyan-3,#d1e4e7)" | <div style="padding:15px 5px">grey-cyan-3</div>
| style="background-color:var(--wikt-palette-grey-cyan-4,#b6d4da)" | <div style="padding:15px 5px">grey-cyan-4</div>
| style="background-color:var(--wikt-palette-grey-cyan-5,#a1c8cf)" | <div style="padding:15px 5px">grey-cyan-5</div>
| style="background-color:var(--wikt-palette-grey-cyan-6,#8cbbc4)" | <div style="padding:15px 5px">grey-cyan-6</div>
| style="background-color:var(--wikt-palette-grey-cyan-7,#86acb2)" | <div style="padding:15px 5px">grey-cyan-7</div>
| style="background-color:var(--wikt-palette-grey-cyan-8,#769da3)" | <div style="padding:15px 5px">grey-cyan-8</div>
| style="background-color:var(--wikt-palette-grey-cyan-9,#577e84)" | <div style="padding:15px 5px">grey-cyan-9</div>
|-
| style="background-color:var(--wikt-palette-grey-blue-0,#f8fafe)" | <div style="padding:15px 5px">grey-blue-0</div>
| style="background-color:var(--wikt-palette-grey-blue-1,#ecf2fc)" | <div style="padding:15px 5px">grey-blue-1</div>
| style="background-color:var(--wikt-palette-grey-blue-2,#e1eafb)" | <div style="padding:15px 5px">grey-blue-2</div>
| style="background-color:var(--wikt-palette-grey-blue-3,#d5e1f9)" | <div style="padding:15px 5px">grey-blue-3</div>
| style="background-color:var(--wikt-palette-grey-blue-4,#bdd0f6)" | <div style="padding:15px 5px">grey-blue-4</div>
| style="background-color:var(--wikt-palette-grey-blue-5,#aec2ea)" | <div style="padding:15px 5px">grey-blue-5</div>
| style="background-color:var(--wikt-palette-grey-blue-6,#9fb4dd)" | <div style="padding:15px 5px">grey-blue-6</div>
| style="background-color:var(--wikt-palette-grey-blue-7,#91a6cf)" | <div style="padding:15px 5px">grey-blue-7</div>
| style="background-color:var(--wikt-palette-grey-blue-8,#8297c0)" | <div style="padding:15px 5px">grey-blue-8</div>
| style="background-color:var(--wikt-palette-grey-blue-9,#6477a0)" | <div style="padding:15px 5px">grey-blue-9</div>
|-
| style="background-color:var(--wikt-palette-grey-indigo-0,#fafafe)" | <div style="padding:15px 5px">grey-indigo-0</div>
| style="background-color:var(--wikt-palette-grey-indigo-1,#f1f1fb)" | <div style="padding:15px 5px">grey-indigo-1</div>
| style="background-color:var(--wikt-palette-grey-indigo-2,#e9e8f9)" | <div style="padding:15px 5px">grey-indigo-2</div>
| style="background-color:var(--wikt-palette-grey-indigo-3,#e0def7)" | <div style="padding:15px 5px">grey-indigo-3</div>
| style="background-color:var(--wikt-palette-grey-indigo-4,#cdcbf2)" | <div style="padding:15px 5px">grey-indigo-4</div>
| style="background-color:var(--wikt-palette-grey-indigo-5,#c0bcee)" | <div style="padding:15px 5px">grey-indigo-5</div>
| style="background-color:var(--wikt-palette-grey-indigo-6,#b2afdc)" | <div style="padding:15px 5px">grey-indigo-6</div>
| style="background-color:var(--wikt-palette-grey-indigo-7,#a4a1d0)" | <div style="padding:15px 5px">grey-indigo-7</div>
| style="background-color:var(--wikt-palette-grey-indigo-8,#9592c2)" | <div style="padding:15px 5px">grey-indigo-8</div>
| style="background-color:var(--wikt-palette-grey-indigo-9,#7572a3)" | <div style="padding:15px 5px">grey-indigo-9</div>
|-
| style="background-color:var(--wikt-palette-grey-purple-0,#fbfafe)" | <div style="padding:15px 5px">grey-purple-0</div>
| style="background-color:var(--wikt-palette-grey-purple-1,#f5f0fc)" | <div style="padding:15px 5px">grey-purple-1</div>
| style="background-color:var(--wikt-palette-grey-purple-2,#eee6fa)" | <div style="padding:15px 5px">grey-purple-2</div>
| style="background-color:var(--wikt-palette-grey-purple-3,#e7dcf8)" | <div style="padding:15px 5px">grey-purple-3</div>
| style="background-color:var(--wikt-palette-grey-purple-4,#d9c8f3)" | <div style="padding:15px 5px">grey-purple-4</div>
| style="background-color:var(--wikt-palette-grey-purple-5,#ceb8f0)" | <div style="padding:15px 5px">grey-purple-5</div>
| style="background-color:var(--wikt-palette-grey-purple-6,#c0aae1)" | <div style="padding:15px 5px">grey-purple-6</div>
| style="background-color:var(--wikt-palette-grey-purple-7,#b19bd3)" | <div style="padding:15px 5px">grey-purple-7</div>
| style="background-color:var(--wikt-palette-grey-purple-8,#a38cc5)" | <div style="padding:15px 5px">grey-purple-8</div>
| style="background-color:var(--wikt-palette-grey-purple-9,#846da5)" | <div style="padding:15px 5px">grey-purple-9</div>
|-
| style="background-color:var(--wikt-palette-grey-magenta-0,#fcf9fd)" | <div style="padding:15px 5px">grey-magenta-0</div>
| style="background-color:var(--wikt-palette-grey-magenta-1,#f7effa)" | <div style="padding:15px 5px">grey-magenta-1</div>
| style="background-color:var(--wikt-palette-grey-magenta-2,#f1e5f7)" | <div style="padding:15px 5px">grey-magenta-2</div>
| style="background-color:var(--wikt-palette-grey-magenta-3,#ebdbf4)" | <div style="padding:15px 5px">grey-magenta-3</div>
| style="background-color:var(--wikt-palette-grey-magenta-4,#e0c6ee)" | <div style="padding:15px 5px">grey-magenta-4</div>
| style="background-color:var(--wikt-palette-grey-magenta-5,#d7b5e9)" | <div style="padding:15px 5px">grey-magenta-5</div>
| style="background-color:var(--wikt-palette-grey-magenta-6,#cda4e4)" | <div style="padding:15px 5px">grey-magenta-6</div>
| style="background-color:var(--wikt-palette-grey-magenta-7,#bc98d0)" | <div style="padding:15px 5px">grey-magenta-7</div>
| style="background-color:var(--wikt-palette-grey-magenta-8,#ae89c2)" | <div style="padding:15px 5px">grey-magenta-8</div>
| style="background-color:var(--wikt-palette-grey-magenta-9,#8e69a2)" | <div style="padding:15px 5px">grey-magenta-9</div>
|-
| style="background-color:var(--wikt-palette-grey-rose-0,#fcfafb)" | <div style="padding:15px 5px">grey-rose-0</div>
| style="background-color:var(--wikt-palette-grey-rose-1,#f6f0f4)" | <div style="padding:15px 5px">grey-rose-1</div>
| style="background-color:var(--wikt-palette-grey-rose-2,#efe7ed)" | <div style="padding:15px 5px">grey-rose-2</div>
| style="background-color:var(--wikt-palette-grey-rose-3,#e9dde6)" | <div style="padding:15px 5px">grey-rose-3</div>
| style="background-color:var(--wikt-palette-grey-rose-4,#ddcad8)" | <div style="padding:15px 5px">grey-rose-4</div>
| style="background-color:var(--wikt-palette-grey-rose-5,#d3bacc)" | <div style="padding:15px 5px">grey-rose-5</div>
| style="background-color:var(--wikt-palette-grey-rose-6,#c9abc1)" | <div style="padding:15px 5px">grey-rose-6</div>
| style="background-color:var(--wikt-palette-grey-rose-7,#be9bb5)" | <div style="padding:15px 5px">grey-rose-7</div>
| style="background-color:var(--wikt-palette-grey-rose-8,#b38aa8)" | <div style="padding:15px 5px">grey-rose-8</div>
| style="background-color:var(--wikt-palette-grey-rose-9,#8c6e84)" | <div style="padding:15px 5px">grey-rose-9</div>
|-
| style="background-color:var(--wikt-palette-grey-brown-0,#fafaf9)" | <div style="padding:15px 5px">grey-brown-0</div>
| style="background-color:var(--wikt-palette-grey-brown-1,#f3f2f0)" | <div style="padding:15px 5px">grey-brown-1</div>
| style="background-color:var(--wikt-palette-grey-brown-2,#ebe9e6)" | <div style="padding:15px 5px">grey-brown-2</div>
| style="background-color:var(--wikt-palette-grey-brown-3,#e4e0dc)" | <div style="padding:15px 5px">grey-brown-3</div>
| style="background-color:var(--wikt-palette-grey-brown-4,#d4cec8)" | <div style="padding:15px 5px">grey-brown-4</div>
| style="background-color:var(--wikt-palette-grey-brown-5,#c8c0b9)" | <div style="padding:15px 5px">grey-brown-5</div>
| style="background-color:var(--wikt-palette-grey-brown-6,#bcb2a9)" | <div style="padding:15px 5px">grey-brown-6</div>
| style="background-color:var(--wikt-palette-grey-brown-7,#afa398)" | <div style="padding:15px 5px">grey-brown-7</div>
| style="background-color:var(--wikt-palette-grey-brown-8,#a19488)" | <div style="padding:15px 5px">grey-brown-8</div>
| style="background-color:var(--wikt-palette-grey-brown-9,#7f766d)" | <div style="padding:15px 5px">grey-brown-9</div>
|-
| style="background-color:var(--wikt-palette-grey-0,#fafafa)" | <div style="padding:15px 5px">grey-0</div>
| style="background-color:var(--wikt-palette-grey-1,#f2f2f2)" | <div style="padding:15px 5px">grey-1</div>
| style="background-color:var(--wikt-palette-grey-2,#e9e9e9)" | <div style="padding:15px 5px">grey-2</div>
| style="background-color:var(--wikt-palette-grey-3,#e1e1e1)" | <div style="padding:15px 5px">grey-3</div>
| style="background-color:var(--wikt-palette-grey-4,#cfcfcf)" | <div style="padding:15px 5px">grey-4</div>
| style="background-color:var(--wikt-palette-grey-5,#c2c2c2)" | <div style="padding:15px 5px">grey-5</div>
| style="background-color:var(--wikt-palette-grey-6,#b4b4b4)" | <div style="padding:15px 5px">grey-6</div>
| style="background-color:var(--wikt-palette-grey-7,#a5a5a5)" | <div style="padding:15px 5px">grey-7</div>
| style="background-color:var(--wikt-palette-grey-8,#979797)" | <div style="padding:15px 5px">grey-8</div>
| style="background-color:var(--wikt-palette-grey-9,#777777)" | <div style="padding:15px 5px">grey-9</div>
|}
{| class="wikt-auto-contrast-exempt" style="background:var(--background-color-base)"
| <div style="color:var(--wikt-palette-red-0,#fef9f9);padding:5px">red-0</div>
| <div style="color:var(--wikt-palette-red-1,#fdeef0);padding:5px">red-1</div>
| <div style="color:var(--wikt-palette-red-2,#fce4e6);padding:5px">red-2</div>
| <div style="color:var(--wikt-palette-red-3,#fad9dc);padding:5px">red-3</div>
| <div style="color:var(--wikt-palette-red-4,#f7c2c6);padding:5px">red-4</div>
| <div style="color:var(--wikt-palette-red-5,#f5afb5);padding:5px">red-5</div>
| <div style="color:var(--wikt-palette-red-6,#f29ba3);padding:5px">red-6</div>
| <div style="color:var(--wikt-palette-red-7,#f0858e);padding:5px">red-7</div>
| <div style="color:var(--wikt-palette-red-8,#ed6d78);padding:5px">red-8</div>
| <div style="color:var(--wikt-palette-red-9,#ce4350);padding:5px">red-9</div>
|-
| <div style="color:var(--wikt-palette-scarlet-0,#fef9f7);padding:5px">scarlet-0</div>
| <div style="color:var(--wikt-palette-scarlet-1,#fcefeb);padding:5px">scarlet-1</div>
| <div style="color:var(--wikt-palette-scarlet-2,#fae5de);padding:5px">scarlet-2</div>
| <div style="color:var(--wikt-palette-scarlet-3,#f8dad1);padding:5px">scarlet-3</div>
| <div style="color:var(--wikt-palette-scarlet-4,#f4c4b5);padding:5px">scarlet-4</div>
| <div style="color:var(--wikt-palette-scarlet-5,#f1b3a0);padding:5px">scarlet-5</div>
| <div style="color:var(--wikt-palette-scarlet-6,#eea088);padding:5px">scarlet-6</div>
| <div style="color:var(--wikt-palette-scarlet-7,#ea8c70);padding:5px">scarlet-7</div>
| <div style="color:var(--wikt-palette-scarlet-8,#e67553);padding:5px">scarlet-8</div>
| <div style="color:var(--wikt-palette-scarlet-9,#c25433);padding:5px">scarlet-9</div>
|-
| <div style="color:var(--wikt-palette-orange-0,#fdfaf5);padding:5px">orange-0</div>
| <div style="color:var(--wikt-palette-orange-1,#fbf0e4);padding:5px">orange-1</div>
| <div style="color:var(--wikt-palette-orange-2,#f8e7d4);padding:5px">orange-2</div>
| <div style="color:var(--wikt-palette-orange-3,#f5ddc3);padding:5px">orange-3</div>
| <div style="color:var(--wikt-palette-orange-4,#f0c89e);padding:5px">orange-4</div>
| <div style="color:var(--wikt-palette-orange-5,#ebb882);padding:5px">orange-5</div>
| <div style="color:var(--wikt-palette-orange-6,#e6a662);padding:5px">orange-6</div>
| <div style="color:var(--wikt-palette-orange-7,#e19341);padding:5px">orange-7</div>
| <div style="color:var(--wikt-palette-orange-8,#db7f1e);padding:5px">orange-8</div>
| <div style="color:var(--wikt-palette-orange-9,#b16210);padding:5px">orange-9</div>
|-
| <div style="color:var(--wikt-palette-amber-0,#fdfaf3);padding:5px">amber-0</div>
| <div style="color:var(--wikt-palette-amber-1,#f9f1df);padding:5px">amber-1</div>
| <div style="color:var(--wikt-palette-amber-2,#f5e8cc);padding:5px">amber-2</div>
| <div style="color:var(--wikt-palette-amber-3,#f1dfb8);padding:5px">amber-3</div>
| <div style="color:var(--wikt-palette-amber-4,#e8cc8f);padding:5px">amber-4</div>
| <div style="color:var(--wikt-palette-amber-5,#e2bc6d);padding:5px">amber-5</div>
| <div style="color:var(--wikt-palette-amber-6,#dbac4a);padding:5px">amber-6</div>
| <div style="color:var(--wikt-palette-amber-7,#d39b24);padding:5px">amber-7</div>
| <div style="color:var(--wikt-palette-amber-8,#c38c17);padding:5px">amber-8</div>
| <div style="color:var(--wikt-palette-amber-9,#9b6f10);padding:5px">amber-9</div>
|-
| <div style="color:var(--wikt-palette-yellow-0,#fbfbf4);padding:5px">yellow-0</div>
| <div style="color:var(--wikt-palette-yellow-1,#f5f2e0);padding:5px">yellow-1</div>
| <div style="color:var(--wikt-palette-yellow-2,#eeeacc);padding:5px">yellow-2</div>
| <div style="color:var(--wikt-palette-yellow-3,#e8e2b9);padding:5px">yellow-3</div>
| <div style="color:var(--wikt-palette-yellow-4,#dbd191);padding:5px">yellow-4</div>
| <div style="color:var(--wikt-palette-yellow-5,#d0c370);padding:5px">yellow-5</div>
| <div style="color:var(--wikt-palette-yellow-6,#c5b44f);padding:5px">yellow-6</div>
| <div style="color:var(--wikt-palette-yellow-7,#b6a63f);padding:5px">yellow-7</div>
| <div style="color:var(--wikt-palette-yellow-8,#a69737);padding:5px">yellow-8</div>
| <div style="color:var(--wikt-palette-yellow-9,#847827);padding:5px">yellow-9</div>
|-
| <div style="color:var(--wikt-palette-lime-0,#f9fbf3);padding:5px">lime-0</div>
| <div style="color:var(--wikt-palette-lime-1,#eff4e1);padding:5px">lime-1</div>
| <div style="color:var(--wikt-palette-lime-2,#e5edce);padding:5px">lime-2</div>
| <div style="color:var(--wikt-palette-lime-3,#dbe5bc);padding:5px">lime-3</div>
| <div style="color:var(--wikt-palette-lime-4,#c5d693);padding:5px">lime-4</div>
| <div style="color:var(--wikt-palette-lime-5,#b5ca73);padding:5px">lime-5</div>
| <div style="color:var(--wikt-palette-lime-6,#a3be52);padding:5px">lime-6</div>
| <div style="color:var(--wikt-palette-lime-7,#97af4e);padding:5px">lime-7</div>
| <div style="color:var(--wikt-palette-lime-8,#889f43);padding:5px">lime-8</div>
| <div style="color:var(--wikt-palette-lime-9,#6b7f2f);padding:5px">lime-9</div>
|-
| <div style="color:var(--wikt-palette-green-0,#f8fbf5);padding:5px">green-0</div>
| <div style="color:var(--wikt-palette-green-1,#eaf5e4);padding:5px">green-1</div>
| <div style="color:var(--wikt-palette-green-2,#ddefd3);padding:5px">green-2</div>
| <div style="color:var(--wikt-palette-green-3,#cee8c1);padding:5px">green-3</div>
| <div style="color:var(--wikt-palette-green-4,#b3da9d);padding:5px">green-4</div>
| <div style="color:var(--wikt-palette-green-5,#9cd080);padding:5px">green-5</div>
| <div style="color:var(--wikt-palette-green-6,#83c460);padding:5px">green-6</div>
| <div style="color:var(--wikt-palette-green-7,#69b840);padding:5px">green-7</div>
| <div style="color:var(--wikt-palette-green-8,#64a640);padding:5px">green-8</div>
| <div style="color:var(--wikt-palette-green-9,#4b852d);padding:5px">green-9</div>
|-
| <div style="color:var(--wikt-palette-teal-0,#f6fcf9);padding:5px">teal-0</div>
| <div style="color:var(--wikt-palette-teal-1,#e5f6ee);padding:5px">teal-1</div>
| <div style="color:var(--wikt-palette-teal-2,#d5efe3);padding:5px">teal-2</div>
| <div style="color:var(--wikt-palette-teal-3,#c3e9d7);padding:5px">teal-3</div>
| <div style="color:var(--wikt-palette-teal-4,#9fdcc0);padding:5px">teal-4</div>
| <div style="color:var(--wikt-palette-teal-5,#82d1ac);padding:5px">teal-5</div>
| <div style="color:var(--wikt-palette-teal-6,#64c599);padding:5px">teal-6</div>
| <div style="color:var(--wikt-palette-teal-7,#41b982);padding:5px">teal-7</div>
| <div style="color:var(--wikt-palette-teal-8,#3fa878);padding:5px">teal-8</div>
| <div style="color:var(--wikt-palette-teal-9,#2c865d);padding:5px">teal-9</div>
|-
| <div style="color:var(--wikt-palette-cyan-0,#f6fbfc);padding:5px">cyan-0</div>
| <div style="color:var(--wikt-palette-cyan-1,#e7f4f7);padding:5px">cyan-1</div>
| <div style="color:var(--wikt-palette-cyan-2,#d7edf1);padding:5px">cyan-2</div>
| <div style="color:var(--wikt-palette-cyan-3,#c7e6ec);padding:5px">cyan-3</div>
| <div style="color:var(--wikt-palette-cyan-4,#a6d7e0);padding:5px">cyan-4</div>
| <div style="color:var(--wikt-palette-cyan-5,#8accd7);padding:5px">cyan-5</div>
| <div style="color:var(--wikt-palette-cyan-6,#6ebfcd);padding:5px">cyan-6</div>
| <div style="color:var(--wikt-palette-cyan-7,#50b2c3);padding:5px">cyan-7</div>
| <div style="color:var(--wikt-palette-cyan-8,#2ea3b7);padding:5px">cyan-8</div>
| <div style="color:var(--wikt-palette-cyan-9,#288191);padding:5px">cyan-9</div>
|-
| <div style="color:var(--wikt-palette-blue-0,#f8fafe);padding:5px">blue-0</div>
| <div style="color:var(--wikt-palette-blue-1,#ecf2fe);padding:5px">blue-1</div>
| <div style="color:var(--wikt-palette-blue-2,#e0eafd);padding:5px">blue-2</div>
| <div style="color:var(--wikt-palette-blue-3,#d3e1fc);padding:5px">blue-3</div>
| <div style="color:var(--wikt-palette-blue-4,#bbd0fa);padding:5px">blue-4</div>
| <div style="color:var(--wikt-palette-blue-5,#a6c2f9);padding:5px">blue-5</div>
| <div style="color:var(--wikt-palette-blue-6,#92b3f7);padding:5px">blue-6</div>
| <div style="color:var(--wikt-palette-blue-7,#7ca4f5);padding:5px">blue-7</div>
| <div style="color:var(--wikt-palette-blue-8,#6494f4);padding:5px">blue-8</div>
| <div style="color:var(--wikt-palette-blue-9,#3d72db);padding:5px">blue-9</div>
|-
| <div style="color:var(--wikt-palette-indigo-0,#fafafe);padding:5px">indigo-0</div>
| <div style="color:var(--wikt-palette-indigo-1,#f1f1fd);padding:5px">indigo-1</div>
| <div style="color:var(--wikt-palette-indigo-2,#e9e7fc);padding:5px">indigo-2</div>
| <div style="color:var(--wikt-palette-indigo-3,#e0defa);padding:5px">indigo-3</div>
| <div style="color:var(--wikt-palette-indigo-4,#cecbf8);padding:5px">indigo-4</div>
| <div style="color:var(--wikt-palette-indigo-5,#bfbcf5);padding:5px">indigo-5</div>
| <div style="color:var(--wikt-palette-indigo-6,#b0acf3);padding:5px">indigo-6</div>
| <div style="color:var(--wikt-palette-indigo-7,#a19cf1);padding:5px">indigo-7</div>
| <div style="color:var(--wikt-palette-indigo-8,#918aef);padding:5px">indigo-8</div>
| <div style="color:var(--wikt-palette-indigo-9,#6e66e0);padding:5px">indigo-9</div>
|-
| <div style="color:var(--wikt-palette-purple-0,#fcfafe);padding:5px">purple-0</div>
| <div style="color:var(--wikt-palette-purple-1,#f5f0fd);padding:5px">purple-1</div>
| <div style="color:var(--wikt-palette-purple-2,#efe6fc);padding:5px">purple-2</div>
| <div style="color:var(--wikt-palette-purple-3,#e8dbfb);padding:5px">purple-3</div>
| <div style="color:var(--wikt-palette-purple-4,#dbc6f8);padding:5px">purple-4</div>
| <div style="color:var(--wikt-palette-purple-5,#d0b6f7);padding:5px">purple-5</div>
| <div style="color:var(--wikt-palette-purple-6,#c5a5f5);padding:5px">purple-6</div>
| <div style="color:var(--wikt-palette-purple-7,#b993f2);padding:5px">purple-7</div>
| <div style="color:var(--wikt-palette-purple-8,#ad7ff0);padding:5px">purple-8</div>
| <div style="color:var(--wikt-palette-purple-9,#9053eb);padding:5px">purple-9</div>
|-
| <div style="color:var(--wikt-palette-magenta-0,#fcf9fe);padding:5px">magenta-0</div>
| <div style="color:var(--wikt-palette-magenta-1,#f7effc);padding:5px">magenta-1</div>
| <div style="color:var(--wikt-palette-magenta-2,#f3e4fa);padding:5px">magenta-2</div>
| <div style="color:var(--wikt-palette-magenta-3,#eedaf9);padding:5px">magenta-3</div>
| <div style="color:var(--wikt-palette-magenta-4,#e3c4f5);padding:5px">magenta-4</div>
| <div style="color:var(--wikt-palette-magenta-5,#dbb2f2);padding:5px">magenta-5</div>
| <div style="color:var(--wikt-palette-magenta-6,#d3a0ef);padding:5px">magenta-6</div>
| <div style="color:var(--wikt-palette-magenta-7,#ca8deb);padding:5px">magenta-7</div>
| <div style="color:var(--wikt-palette-magenta-8,#c078e8);padding:5px">magenta-8</div>
| <div style="color:var(--wikt-palette-magenta-9,#a844df);padding:5px">magenta-9</div>
|-
| <div style="color:var(--wikt-palette-rose-0,#fcf9fc);padding:5px">rose-0</div>
| <div style="color:var(--wikt-palette-rose-1,#f8f0f6);padding:5px">rose-1</div>
| <div style="color:var(--wikt-palette-rose-2,#f3e6ef);padding:5px">rose-2</div>
| <div style="color:var(--wikt-palette-rose-3,#eedbe9);padding:5px">rose-3</div>
| <div style="color:var(--wikt-palette-rose-4,#e4c7dc);padding:5px">rose-4</div>
| <div style="color:var(--wikt-palette-rose-5,#dcb6d2);padding:5px">rose-5</div>
| <div style="color:var(--wikt-palette-rose-6,#d4a5c8);padding:5px">rose-6</div>
| <div style="color:var(--wikt-palette-rose-7,#cc94bd);padding:5px">rose-7</div>
| <div style="color:var(--wikt-palette-rose-8,#c381b1);padding:5px">rose-8</div>
| <div style="color:var(--wikt-palette-rose-9,#af5797);padding:5px">rose-9</div>
|-
| <div style="color:var(--wikt-palette-brown-0,#fbfaf9);padding:5px">brown-0</div>
| <div style="color:var(--wikt-palette-brown-1,#f4f1ee);padding:5px">brown-1</div>
| <div style="color:var(--wikt-palette-brown-2,#eee8e4);padding:5px">brown-2</div>
| <div style="color:var(--wikt-palette-brown-3,#e7dfd9);padding:5px">brown-3</div>
| <div style="color:var(--wikt-palette-brown-4,#d8cdc3);padding:5px">brown-4</div>
| <div style="color:var(--wikt-palette-brown-5,#cdbfb2);padding:5px">brown-5</div>
| <div style="color:var(--wikt-palette-brown-6,#c2b1a1);padding:5px">brown-6</div>
| <div style="color:var(--wikt-palette-brown-7,#b7a18f);padding:5px">brown-7</div>
| <div style="color:var(--wikt-palette-brown-8,#ab927c);padding:5px">brown-8</div>
| <div style="color:var(--wikt-palette-brown-9,#917155);padding:5px">brown-9</div>
|-
| <div style="color:var(--wikt-palette-grey-red-0,#fef9f9);padding:5px">grey-red-0</div>
| <div style="color:var(--wikt-palette-grey-red-1,#fbeff0);padding:5px">grey-red-1</div>
| <div style="color:var(--wikt-palette-grey-red-2,#f9e5e6);padding:5px">grey-red-2</div>
| <div style="color:var(--wikt-palette-grey-red-3,#f7dadc);padding:5px">grey-red-3</div>
| <div style="color:var(--wikt-palette-grey-red-4,#f2c3c8);padding:5px">grey-red-4</div>
| <div style="color:var(--wikt-palette-grey-red-5,#eeb2b7);padding:5px">grey-red-5</div>
| <div style="color:var(--wikt-palette-grey-red-6,#dca5ab);padding:5px">grey-red-6</div>
| <div style="color:var(--wikt-palette-grey-red-7,#ce979c);padding:5px">grey-red-7</div>
| <div style="color:var(--wikt-palette-grey-red-8,#c0878c);padding:5px">grey-red-8</div>
| <div style="color:var(--wikt-palette-grey-red-9,#9f686d);padding:5px">grey-red-9</div>
|-
| <div style="color:var(--wikt-palette-grey-scarlet-0,#fdfaf8);padding:5px">grey-scarlet-0</div>
| <div style="color:var(--wikt-palette-grey-scarlet-1,#faf0ec);padding:5px">grey-scarlet-1</div>
| <div style="color:var(--wikt-palette-grey-scarlet-2,#f7e6e0);padding:5px">grey-scarlet-2</div>
| <div style="color:var(--wikt-palette-grey-scarlet-3,#f4dcd4);padding:5px">grey-scarlet-3</div>
| <div style="color:var(--wikt-palette-grey-scarlet-4,#eec7bb);padding:5px">grey-scarlet-4</div>
| <div style="color:var(--wikt-palette-grey-scarlet-5,#e8b6a7);padding:5px">grey-scarlet-5</div>
| <div style="color:var(--wikt-palette-grey-scarlet-6,#d6a99c);padding:5px">grey-scarlet-6</div>
| <div style="color:var(--wikt-palette-grey-scarlet-7,#c89b8d);padding:5px">grey-scarlet-7</div>
| <div style="color:var(--wikt-palette-grey-scarlet-8,#b98c7e);padding:5px">grey-scarlet-8</div>
| <div style="color:var(--wikt-palette-grey-scarlet-9,#986d5f);padding:5px">grey-scarlet-9</div>
|-
| <div style="color:var(--wikt-palette-grey-orange-0,#fdfaf6);padding:5px">grey-orange-0</div>
| <div style="color:var(--wikt-palette-grey-orange-1,#f9f0e8);padding:5px">grey-orange-1</div>
| <div style="color:var(--wikt-palette-grey-orange-2,#f4e7d9);padding:5px">grey-orange-2</div>
| <div style="color:var(--wikt-palette-grey-orange-3,#f0deca);padding:5px">grey-orange-3</div>
| <div style="color:var(--wikt-palette-grey-orange-4,#e8caaa);padding:5px">grey-orange-4</div>
| <div style="color:var(--wikt-palette-grey-orange-5,#e1bb92);padding:5px">grey-orange-5</div>
| <div style="color:var(--wikt-palette-grey-orange-6,#cfae8a);padding:5px">grey-orange-6</div>
| <div style="color:var(--wikt-palette-grey-orange-7,#c19f7c);padding:5px">grey-orange-7</div>
| <div style="color:var(--wikt-palette-grey-orange-8,#b2906d);padding:5px">grey-orange-8</div>
| <div style="color:var(--wikt-palette-grey-orange-9,#917151);padding:5px">grey-orange-9</div>
|-
| <div style="color:var(--wikt-palette-grey-amber-0,#fcfaf5);padding:5px">grey-amber-0</div>
| <div style="color:var(--wikt-palette-grey-amber-1,#f7f1e6);padding:5px">grey-amber-1</div>
| <div style="color:var(--wikt-palette-grey-amber-2,#f1e9d6);padding:5px">grey-amber-2</div>
| <div style="color:var(--wikt-palette-grey-amber-3,#ece0c6);padding:5px">grey-amber-3</div>
| <div style="color:var(--wikt-palette-grey-amber-4,#e0cda3);padding:5px">grey-amber-4</div>
| <div style="color:var(--wikt-palette-grey-amber-5,#d8bf8a);padding:5px">grey-amber-5</div>
| <div style="color:var(--wikt-palette-grey-amber-6,#c6b185);padding:5px">grey-amber-6</div>
| <div style="color:var(--wikt-palette-grey-amber-7,#b7a377);padding:5px">grey-amber-7</div>
| <div style="color:var(--wikt-palette-grey-amber-8,#a89468);padding:5px">grey-amber-8</div>
| <div style="color:var(--wikt-palette-grey-amber-9,#88754c);padding:5px">grey-amber-9</div>
|-
| <div style="color:var(--wikt-palette-grey-yellow-0,#fbfaf6);padding:5px">grey-yellow-0</div>
| <div style="color:var(--wikt-palette-grey-yellow-1,#f3f2e7);padding:5px">grey-yellow-1</div>
| <div style="color:var(--wikt-palette-grey-yellow-2,#ecead9);padding:5px">grey-yellow-2</div>
| <div style="color:var(--wikt-palette-grey-yellow-3,#e5e1ca);padding:5px">grey-yellow-3</div>
| <div style="color:var(--wikt-palette-grey-yellow-4,#d5d0ab);padding:5px">grey-yellow-4</div>
| <div style="color:var(--wikt-palette-grey-yellow-5,#cac394);padding:5px">grey-yellow-5</div>
| <div style="color:var(--wikt-palette-grey-yellow-6,#beb57c);padding:5px">grey-yellow-6</div>
| <div style="color:var(--wikt-palette-grey-yellow-7,#aca67f);padding:5px">grey-yellow-7</div>
| <div style="color:var(--wikt-palette-grey-yellow-8,#9e9770);padding:5px">grey-yellow-8</div>
| <div style="color:var(--wikt-palette-grey-yellow-9,#7e7851);padding:5px">grey-yellow-9</div>
|-
| <div style="color:var(--wikt-palette-grey-lime-0,#fafbf7);padding:5px">grey-lime-0</div>
| <div style="color:var(--wikt-palette-grey-lime-1,#f0f3e8);padding:5px">grey-lime-1</div>
| <div style="color:var(--wikt-palette-grey-lime-2,#e7ebda);padding:5px">grey-lime-2</div>
| <div style="color:var(--wikt-palette-grey-lime-3,#dee3cc);padding:5px">grey-lime-3</div>
| <div style="color:var(--wikt-palette-grey-lime-4,#cad3ae);padding:5px">grey-lime-4</div>
| <div style="color:var(--wikt-palette-grey-lime-5,#bbc798);padding:5px">grey-lime-5</div>
| <div style="color:var(--wikt-palette-grey-lime-6,#abba81);padding:5px">grey-lime-6</div>
| <div style="color:var(--wikt-palette-grey-lime-7,#a0a984);padding:5px">grey-lime-7</div>
| <div style="color:var(--wikt-palette-grey-lime-8,#919b74);padding:5px">grey-lime-8</div>
| <div style="color:var(--wikt-palette-grey-lime-9,#727c55);padding:5px">grey-lime-9</div>
|-
| <div style="color:var(--wikt-palette-grey-green-0,#f8fbf7);padding:5px">grey-green-0</div>
| <div style="color:var(--wikt-palette-grey-green-1,#edf4ea);padding:5px">grey-green-1</div>
| <div style="color:var(--wikt-palette-grey-green-2,#e2ecdd);padding:5px">grey-green-2</div>
| <div style="color:var(--wikt-palette-grey-green-3,#d6e5ce);padding:5px">grey-green-3</div>
| <div style="color:var(--wikt-palette-grey-green-4,#bfd6b3);padding:5px">grey-green-4</div>
| <div style="color:var(--wikt-palette-grey-green-5,#acca9d);padding:5px">grey-green-5</div>
| <div style="color:var(--wikt-palette-grey-green-6,#9abe87);padding:5px">grey-green-6</div>
| <div style="color:var(--wikt-palette-grey-green-7,#93ad86);padding:5px">grey-green-7</div>
| <div style="color:var(--wikt-palette-grey-green-8,#839e75);padding:5px">grey-green-8</div>
| <div style="color:var(--wikt-palette-grey-green-9,#647f56);padding:5px">grey-green-9</div>
|-
| <div style="color:var(--wikt-palette-grey-teal-0,#f7fbf9);padding:5px">grey-teal-0</div>
| <div style="color:var(--wikt-palette-grey-teal-1,#eaf4ef);padding:5px">grey-teal-1</div>
| <div style="color:var(--wikt-palette-grey-teal-2,#ddede5);padding:5px">grey-teal-2</div>
| <div style="color:var(--wikt-palette-grey-teal-3,#d0e6dc);padding:5px">grey-teal-3</div>
| <div style="color:var(--wikt-palette-grey-teal-4,#b4d7c7);padding:5px">grey-teal-4</div>
| <div style="color:var(--wikt-palette-grey-teal-5,#9ecbb6);padding:5px">grey-teal-5</div>
| <div style="color:var(--wikt-palette-grey-teal-6,#88bfa5);padding:5px">grey-teal-6</div>
| <div style="color:var(--wikt-palette-grey-teal-7,#86ae9b);padding:5px">grey-teal-7</div>
| <div style="color:var(--wikt-palette-grey-teal-8,#769f8c);padding:5px">grey-teal-8</div>
| <div style="color:var(--wikt-palette-grey-teal-9,#56806d);padding:5px">grey-teal-9</div>
|-
| <div style="color:var(--wikt-palette-grey-cyan-0,#f7fbfb);padding:5px">grey-cyan-0</div>
| <div style="color:var(--wikt-palette-grey-cyan-1,#ebf3f5);padding:5px">grey-cyan-1</div>
| <div style="color:var(--wikt-palette-grey-cyan-2,#deecee);padding:5px">grey-cyan-2</div>
| <div style="color:var(--wikt-palette-grey-cyan-3,#d1e4e7);padding:5px">grey-cyan-3</div>
| <div style="color:var(--wikt-palette-grey-cyan-4,#b6d4da);padding:5px">grey-cyan-4</div>
| <div style="color:var(--wikt-palette-grey-cyan-5,#a1c8cf);padding:5px">grey-cyan-5</div>
| <div style="color:var(--wikt-palette-grey-cyan-6,#8cbbc4);padding:5px">grey-cyan-6</div>
| <div style="color:var(--wikt-palette-grey-cyan-7,#86acb2);padding:5px">grey-cyan-7</div>
| <div style="color:var(--wikt-palette-grey-cyan-8,#769da3);padding:5px">grey-cyan-8</div>
| <div style="color:var(--wikt-palette-grey-cyan-9,#577e84);padding:5px">grey-cyan-9</div>
|-
| <div style="color:var(--wikt-palette-grey-blue-0,#f8fafe);padding:5px">grey-blue-0</div>
| <div style="color:var(--wikt-palette-grey-blue-1,#ecf2fc);padding:5px">grey-blue-1</div>
| <div style="color:var(--wikt-palette-grey-blue-2,#e1eafb);padding:5px">grey-blue-2</div>
| <div style="color:var(--wikt-palette-grey-blue-3,#d5e1f9);padding:5px">grey-blue-3</div>
| <div style="color:var(--wikt-palette-grey-blue-4,#bdd0f6);padding:5px">grey-blue-4</div>
| <div style="color:var(--wikt-palette-grey-blue-5,#aec2ea);padding:5px">grey-blue-5</div>
| <div style="color:var(--wikt-palette-grey-blue-6,#9fb4dd);padding:5px">grey-blue-6</div>
| <div style="color:var(--wikt-palette-grey-blue-7,#91a6cf);padding:5px">grey-blue-7</div>
| <div style="color:var(--wikt-palette-grey-blue-8,#8297c0);padding:5px">grey-blue-8</div>
| <div style="color:var(--wikt-palette-grey-blue-9,#6477a0);padding:5px">grey-blue-9</div>
|-
| <div style="color:var(--wikt-palette-grey-indigo-0,#fafafe);padding:5px">grey-indigo-0</div>
| <div style="color:var(--wikt-palette-grey-indigo-1,#f1f1fb);padding:5px">grey-indigo-1</div>
| <div style="color:var(--wikt-palette-grey-indigo-2,#e9e8f9);padding:5px">grey-indigo-2</div>
| <div style="color:var(--wikt-palette-grey-indigo-3,#e0def7);padding:5px">grey-indigo-3</div>
| <div style="color:var(--wikt-palette-grey-indigo-4,#cdcbf2);padding:5px">grey-indigo-4</div>
| <div style="color:var(--wikt-palette-grey-indigo-5,#c0bcee);padding:5px">grey-indigo-5</div>
| <div style="color:var(--wikt-palette-grey-indigo-6,#b2afdc);padding:5px">grey-indigo-6</div>
| <div style="color:var(--wikt-palette-grey-indigo-7,#a4a1d0);padding:5px">grey-indigo-7</div>
| <div style="color:var(--wikt-palette-grey-indigo-8,#9592c2);padding:5px">grey-indigo-8</div>
| <div style="color:var(--wikt-palette-grey-indigo-9,#7572a3);padding:5px">grey-indigo-9</div>
|-
| <div style="color:var(--wikt-palette-grey-purple-0,#fbfafe);padding:5px">grey-purple-0</div>
| <div style="color:var(--wikt-palette-grey-purple-1,#f5f0fc);padding:5px">grey-purple-1</div>
| <div style="color:var(--wikt-palette-grey-purple-2,#eee6fa);padding:5px">grey-purple-2</div>
| <div style="color:var(--wikt-palette-grey-purple-3,#e7dcf8);padding:5px">grey-purple-3</div>
| <div style="color:var(--wikt-palette-grey-purple-4,#d9c8f3);padding:5px">grey-purple-4</div>
| <div style="color:var(--wikt-palette-grey-purple-5,#ceb8f0);padding:5px">grey-purple-5</div>
| <div style="color:var(--wikt-palette-grey-purple-6,#c0aae1);padding:5px">grey-purple-6</div>
| <div style="color:var(--wikt-palette-grey-purple-7,#b19bd3);padding:5px">grey-purple-7</div>
| <div style="color:var(--wikt-palette-grey-purple-8,#a38cc5);padding:5px">grey-purple-8</div>
| <div style="color:var(--wikt-palette-grey-purple-9,#846da5);padding:5px">grey-purple-9</div>
|-
| <div style="color:var(--wikt-palette-grey-magenta-0,#fcf9fd);padding:5px">grey-magenta-0</div>
| <div style="color:var(--wikt-palette-grey-magenta-1,#f7effa);padding:5px">grey-magenta-1</div>
| <div style="color:var(--wikt-palette-grey-magenta-2,#f1e5f7);padding:5px">grey-magenta-2</div>
| <div style="color:var(--wikt-palette-grey-magenta-3,#ebdbf4);padding:5px">grey-magenta-3</div>
| <div style="color:var(--wikt-palette-grey-magenta-4,#e0c6ee);padding:5px">grey-magenta-4</div>
| <div style="color:var(--wikt-palette-grey-magenta-5,#d7b5e9);padding:5px">grey-magenta-5</div>
| <div style="color:var(--wikt-palette-grey-magenta-6,#cda4e4);padding:5px">grey-magenta-6</div>
| <div style="color:var(--wikt-palette-grey-magenta-7,#bc98d0);padding:5px">grey-magenta-7</div>
| <div style="color:var(--wikt-palette-grey-magenta-8,#ae89c2);padding:5px">grey-magenta-8</div>
| <div style="color:var(--wikt-palette-grey-magenta-9,#8e69a2);padding:5px">grey-magenta-9</div>
|-
| <div style="color:var(--wikt-palette-grey-rose-0,#fcfafb);padding:5px">grey-rose-0</div>
| <div style="color:var(--wikt-palette-grey-rose-1,#f6f0f4);padding:5px">grey-rose-1</div>
| <div style="color:var(--wikt-palette-grey-rose-2,#efe7ed);padding:5px">grey-rose-2</div>
| <div style="color:var(--wikt-palette-grey-rose-3,#e9dde6);padding:5px">grey-rose-3</div>
| <div style="color:var(--wikt-palette-grey-rose-4,#ddcad8);padding:5px">grey-rose-4</div>
| <div style="color:var(--wikt-palette-grey-rose-5,#d3bacc);padding:5px">grey-rose-5</div>
| <div style="color:var(--wikt-palette-grey-rose-6,#c9abc1);padding:5px">grey-rose-6</div>
| <div style="color:var(--wikt-palette-grey-rose-7,#be9bb5);padding:5px">grey-rose-7</div>
| <div style="color:var(--wikt-palette-grey-rose-8,#b38aa8);padding:5px">grey-rose-8</div>
| <div style="color:var(--wikt-palette-grey-rose-9,#8c6e84);padding:5px">grey-rose-9</div>
|-
| <div style="color:var(--wikt-palette-grey-brown-0,#fafaf9);padding:5px">grey-brown-0</div>
| <div style="color:var(--wikt-palette-grey-brown-1,#f3f2f0);padding:5px">grey-brown-1</div>
| <div style="color:var(--wikt-palette-grey-brown-2,#ebe9e6);padding:5px">grey-brown-2</div>
| <div style="color:var(--wikt-palette-grey-brown-3,#e4e0dc);padding:5px">grey-brown-3</div>
| <div style="color:var(--wikt-palette-grey-brown-4,#d4cec8);padding:5px">grey-brown-4</div>
| <div style="color:var(--wikt-palette-grey-brown-5,#c8c0b9);padding:5px">grey-brown-5</div>
| <div style="color:var(--wikt-palette-grey-brown-6,#bcb2a9);padding:5px">grey-brown-6</div>
| <div style="color:var(--wikt-palette-grey-brown-7,#afa398);padding:5px">grey-brown-7</div>
| <div style="color:var(--wikt-palette-grey-brown-8,#a19488);padding:5px">grey-brown-8</div>
| <div style="color:var(--wikt-palette-grey-brown-9,#7f766d);padding:5px">grey-brown-9</div>
|-
| <div style="color:var(--wikt-palette-grey-0,#fafafa);padding:5px">grey-0</div>
| <div style="color:var(--wikt-palette-grey-1,#f2f2f2);padding:5px">grey-1</div>
| <div style="color:var(--wikt-palette-grey-2,#e9e9e9);padding:5px">grey-2</div>
| <div style="color:var(--wikt-palette-grey-3,#e1e1e1);padding:5px">grey-3</div>
| <div style="color:var(--wikt-palette-grey-4,#cfcfcf);padding:5px">grey-4</div>
| <div style="color:var(--wikt-palette-grey-5,#c2c2c2);padding:5px">grey-5</div>
| <div style="color:var(--wikt-palette-grey-6,#b4b4b4);padding:5px">grey-6</div>
| <div style="color:var(--wikt-palette-grey-7,#a5a5a5);padding:5px">grey-7</div>
| <div style="color:var(--wikt-palette-grey-8,#979797);padding:5px">grey-8</div>
| <div style="color:var(--wikt-palette-grey-9,#777777);padding:5px">grey-9</div>
|}
c8qjaobhznfjgywibx5c10d3sfm0467
Modul:palette
828
1358187
6291603
2026-07-26T20:16:56Z
Wikihez
7344
[[:en:Module:palette]] hat kopîkirin ([[Bikarhêner:Wikihez/skrîpt/js/kopi-enWikt.js|kopi-enWikt.js]])
6291603
Scribunto
text/plain
local export = {}
-- Given a CSS hex value, return a table of the RGB components.
-- The RGB is normalized so that white is (1.0, 1.0, 1.0).
local function get_rgb_from_hex(hex_colour)
hex_colour = hex_colour:gsub("#", "")
if #hex_colour == 3 then
return {
tonumber(hex_colour:sub(1, 1), 16) * 17 / 255,
tonumber(hex_colour:sub(2, 2), 16) * 17 / 255,
tonumber(hex_colour:sub(3, 3), 16) * 17 / 255
}
elseif #hex_colour == 6 then
return {
tonumber(hex_colour:sub(1, 2), 16) / 255,
tonumber(hex_colour:sub(3, 4), 16) / 255,
tonumber(hex_colour:sub(5, 6), 16) / 255
}
end
end
-- Calculate the WCAG2.1 relative luminance value given a RGB tuple.
-- https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html#dfn-relative-luminance
local function get_WCAG_21_luminance(rgb)
local luminance_parts = {}
for _, rgb_part in ipairs(rgb) do
if rgb_part > 0.04045 then
table.insert(luminance_parts, ((rgb_part + 0.055) / 1.055) ^ 2.4)
else
table.insert(luminance_parts, rgb_part / 12.92)
end
end
local luminance = 0.2126 * luminance_parts[1] + 0.7152 * luminance_parts[2] + 0.0722 * luminance_parts[3]
return luminance
end
-- Calculate the WCAG2.1 contrast ratio given two RGB tuples.
-- https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html#dfn-contrast-ratio
local function get_WCAG_21_contrast(rgb1, rgb2)
local luminance1 = get_WCAG_21_luminance(rgb1)
local luminance2 = get_WCAG_21_luminance(rgb2)
-- The lighter colour is used in the numerator.
local contrast_ratio
if luminance1 >= luminance2 then
contrast_ratio = (luminance1 + 0.05) / (luminance2 + 0.05)
else
contrast_ratio = (luminance2 + 0.05) / (luminance1 + 0.05)
end
return contrast_ratio
end
-- Calculate the APCA contrast ratio given a text RGB tuple and a background RGB tuple.
-- https://github.com/Myndex/SAPC-APCA/blob/master/documentation/APCA-W3-LaTeX.md
-- The variables are named to match the LaTeX document.
local function get_APCA_contrast(rgb_txt, rgb_bg)
local Ys_txt = rgb_txt[1] ^ 2.4 * 0.2126729 + rgb_txt[2] ^ 2.4 * 0.7151522 + rgb_txt[3] ^ 2.4 * 0.0721750
local Ys_bg = rgb_bg[1] ^ 2.4 * 0.2126729 + rgb_bg[2] ^ 2.4 * 0.7151522 + rgb_bg[3] ^ 2.4 * 0.0721750
local function f_sc(Y_c)
if Y_c <= 0 then
return 0
elseif Y_c <= 0.022 then
return Y_c + (0.022 - Y_c) ^ 1.414
else
return Y_c
end
end
local Y_txt = f_sc(Ys_txt)
local Y_bg = f_sc(Ys_bg)
local S_apc
if Y_bg > Y_txt then
S_apc = (Y_bg ^ 0.56 - Y_txt ^ 0.57) * 1.14
else
S_apc = (Y_bg ^ 0.65 - Y_txt ^ 0.62) * 1.14
end
local L_c
if math.abs(S_apc) < 0.1 then
L_c = 0
elseif S_apc > 0 then
L_c = (S_apc - 0.027) * 100
else
L_c = (S_apc + 0.027) * 100
end
return L_c
end
-- Get the WCAG2.1 rating as HTML, given a contrast value.
local function get_WCAG_21_rating(contrast_value)
if contrast_value >= 7 then
return "<span style=\"color:var(--wikt-palette-deepblue)\">(AAA)</span>"
elseif contrast_value >= 4.5 then
return "<span style=\"color:var(--wikt-palette-forestgreen)\">(AA)</span>"
else
return "<span style=\"color:var(--wikt-palette-red)\">(FAIL)</span>"
end
end
-- Get the APCA rating as HTML, given a contrast value.
-- Using default text values: font size = 14px, font weight = 400
local function get_APCA_rating(contrast_value)
local abs_contrast_value = math.abs(contrast_value)
if abs_contrast_value >= 100 then
return "<span style=\"color:var(--wikt-palette-deepblue)\">(R4)</span>"
elseif abs_contrast_value >= 95 then
return "<span style=\"color:var(--wikt-palette-forestgreen)\">(R3)</span>"
elseif abs_contrast_value >= 90 then
return "<span style=\"color:var(--wikt-palette-forestgreen)\">(R2)</span>"
elseif abs_contrast_value >= 85 then
return "<span style=\"color:var(--wikt-palette-forestgreen)\">(R1)</span>"
else
return "<span style=\"color:var(--wikt-palette-red)\">(R0)</span>"
end
end
function export.show(frame)
local pageContent = mw.title.new("MediaWiki:Gadget-Palette.css"):getContent()
local lightMode = true;
local lightModeData = {}
local darkModeData = {}
local commentsData = {}
local seenVariables = {} -- In order to maintain the order.
local output = {
[=[{| class="palette-table wikitable mw-no-invert"
|+Table of palette variables in light and dark mode
|-
!scope="col"| Variable name
!scope="col"| Light mode
!scope="col"| Dark mode
!scope="col"| Use as a background colour?
!scope="col"| Use as a text colour?
!scope="col"| Comments
|-"
]=]}
for line in pageContent:gmatch("[^\r\n]*") do
-- Hack to check whether we've reached the dark mode styles.
if line == "@media screen {" then
lightMode = false;
end
local definedVariable = line:match("(--wikt%-palette%-[a-z0-9-]+):")
if definedVariable then
if lightMode then
table.insert(seenVariables, definedVariable)
lightModeData[definedVariable] = line:match("#[a-zA-Z0-9]+")
commentsData[definedVariable] = line:match("/%*%s*(.-)%s*%*/") or ""
else
darkModeData[definedVariable] = line:match("#[a-zA-Z0-9]+")
end
end
end
for _, var in ipairs(seenVariables) do
table.insert(output, "|<code>" .. var .. "</code>")
table.insert(output, "||style=\"background:" .. lightModeData[var] .. "\"|<span class=\"palette-highlight\">" .. lightModeData[var] .. "</span>")
table.insert(output, "||style=\"background:" .. darkModeData[var] .. "\"|<span class=\"palette-highlight\">" .. darkModeData[var] .. "</span>")
-- Contrast on body text in light mode.
local WCAG_21_contrast_text_lightmode = get_WCAG_21_contrast(get_rgb_from_hex("#202122"), get_rgb_from_hex(lightModeData[var]))
local APCA_contrast_text_lightmode = get_APCA_contrast(get_rgb_from_hex("#202122"), get_rgb_from_hex(lightModeData[var]))
table.insert(output, "||'''Light mode:''' WCAG " .. string.format("%.2f", WCAG_21_contrast_text_lightmode) .. " " .. get_WCAG_21_rating(WCAG_21_contrast_text_lightmode))
table.insert(output, ", APCA " .. string.format("%.2f", APCA_contrast_text_lightmode) .. " " .. get_APCA_rating(APCA_contrast_text_lightmode))
-- Contrast on body text in dark mode.
local WCAG_21_contrast_text_darkmode = get_WCAG_21_contrast(get_rgb_from_hex("#eaecf0"), get_rgb_from_hex(darkModeData[var]))
local APCA_contrast_text_darkmode = get_APCA_contrast(get_rgb_from_hex("#eaecf0"), get_rgb_from_hex(darkModeData[var]))
table.insert(output, "<br> '''Dark mode:''' WCAG " .. string.format("%.2f", WCAG_21_contrast_text_darkmode) .. " " .. get_WCAG_21_rating(WCAG_21_contrast_text_darkmode))
table.insert(output, ", APCA " .. string.format("%.2f", APCA_contrast_text_darkmode) .. " " .. get_APCA_rating(APCA_contrast_text_darkmode))
-- Contrast on default background in light mode.
local WCAG_21_contrast_bg_lightmode = get_WCAG_21_contrast(get_rgb_from_hex(lightModeData[var]), get_rgb_from_hex("#ffffff"))
local APCA_contrast_bg_lightmode = get_APCA_contrast(get_rgb_from_hex(lightModeData[var]), get_rgb_from_hex("#ffffff"))
table.insert(output, "||'''Light mode:''' WCAG " .. string.format("%.2f", WCAG_21_contrast_bg_lightmode) .. " " .. get_WCAG_21_rating(WCAG_21_contrast_bg_lightmode))
table.insert(output, ", APCA " .. string.format("%.2f", APCA_contrast_bg_lightmode) .. " " .. get_APCA_rating(APCA_contrast_bg_lightmode))
-- Contrast on default background in dark mode.
local WCAG_21_contrast_bg_darkmode = get_WCAG_21_contrast(get_rgb_from_hex(darkModeData[var]), get_rgb_from_hex("#101418"))
local APCA_contrast_bg_darkmode = get_APCA_contrast(get_rgb_from_hex(darkModeData[var]), get_rgb_from_hex("#101418"))
table.insert(output, "<br> '''Dark mode:''' WCAG " .. string.format("%.2f", WCAG_21_contrast_bg_darkmode) .. " " .. get_WCAG_21_rating(WCAG_21_contrast_bg_darkmode))
table.insert(output, ", APCA " .. string.format("%.2f", APCA_contrast_bg_darkmode) .. " " .. get_APCA_rating(APCA_contrast_bg_darkmode))
-- Comments.
table.insert(output, "||" .. commentsData[var])
table.insert(output, "\n|-\n")
end
table.insert(output, "|-\n")
table.insert(output, "|colspan=\"6\" style=\"padding:1em;background:var(--wikt-palette-lavender,#f8f8ff)\"|According to [[mw:Recommendations for night mode compatibility on Wikimedia wikis]], editors should ensure that colours meet the WCAC 2.1 AA contrast standards (APCA is significantly stricter but corresponds more closely to human visual perception — see [[phab:T308772]]). Contrast ratios are calculated assuming standard text formatting (14px, unbolded) and standard colours on the rest of the page. To check contrast values between any two colours, see https://contrast.tools/.\n")
table.insert(output, "\n|}")
table.insert(output, frame:extensionTag("templatestyles", "", {src="Module:palette/styles.css"}))
return table.concat(output)
end
return export
l4m388x9nzb3p0b550nabnszjk5eux4
Modul:palette/belge
828
1358188
6291604
2026-07-26T20:16:57Z
Wikihez
7344
[[:en:Module:palette/documentation]] hat kopîkirin ([[Bikarhêner:Wikihez/skrîpt/js/kopi-enWikt.js|kopi-enWikt.js]])
6291604
wikitext
text/x-wiki
Generates a table of [[MediaWiki:Gadget-Palette.css|Palette]] variables along with contrast information. See [[MediaWiki:Gadget-Palette/table]].
b1fx0wwaqrxvqxpa1hbe49ty83du4pb
MediaWiki:Gadget-Palette/table
8
1358189
6291605
2026-07-26T20:18:50Z
Wikihez
7344
Rûpel bi "The Palette variables shown below are defined in [[MediaWiki:Gadget-Palette.css]]. To request a new variable to be added, do so [[MediaWiki talk:Gadget-Palette|here]] using this format: <code><name> <light mode hex code> <dark mode hex code></code>. See [[Wiktionary:Palette]] for more information. {{#invoke:palette|show}}" hat çêkirin
6291605
wikitext
text/x-wiki
The Palette variables shown below are defined in [[MediaWiki:Gadget-Palette.css]]. To request a new variable to be added, do so [[MediaWiki talk:Gadget-Palette|here]] using this format: <code><name> <light mode hex code> <dark mode hex code></code>. See [[Wiktionary:Palette]] for more information.
{{#invoke:palette|show}}
ibrl7ml1mx496004xkqd3btqmrqgn3k
Modul:palette/styles.css
828
1358190
6291606
2026-07-26T20:20:09Z
Wikihez
7344
[[:en:Module:palette/styles.css]] hat kopîkirin ([[Bikarhêner:Wikihez/skrîpt/js/kopi-enWikt.js|kopi-enWikt.js]])
6291606
sanitized-css
text/css
.palette-table td:not(:first-child) {
text-align: center;
}
.palette-table td:nth-child(4), .palette-table td:nth-child(5) {
min-width: 200px;
}
.palette-highlight {
position: relative;
z-index: 0;
padding: 2px 5px;
background: var(--wikt-palette-lavender);
opacity: 0.8;
border-radius: 3px;
}
r22pfpm1zg2w8e1xod26e3llyprz737