Wîkîferheng
kuwiktionary
https://ku.wiktionary.org/wiki/Destp%C3%AAk
MediaWiki 1.47.0-wmf.20
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
çûn
0
3143
6535029
6426567
2026-09-20T21:35:46Z
~2026-50815-48
15385
/* Lêker */
6535029
wikitext
text/x-wiki
{{h-bnr|cun|cûn|cün|çun|çûn|çün}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/t͡ʃʰuːn/}}
* {{deng|ku|LL-Q36368 (kur)-Dildadil-çûn.wav|dever=Amed}}
* {{deng|ku|LL-Q36163 (kmr)-Ebulfîda-çûn.wav|Deng|dever=Bidlîs}}
* {{ku-kîte}}
=== Lêker ===
{{ku-tewîn-lk|çûn|form=negerguhêz|niha=her-er-r|niha2=ç|niha3=her|borî=çû}}
[[Wêne:Elephant_Walking_animated.gif|thumb|Fîlek bi rê ve '''diçe'''/'''tere'''.]]
{{lêker|ku|c=x-ngh}}
# Ji [[der]]ekê [[dûr ketin]], [[cih]]ek [[bi cih hêlan]] / [[bi cih hiştin]], [[dûr]]î derekê bûn.
#: {{hevmane|ku|herîn|rewtin|bi rê ketin}}
#: {{dijmane|ku|hatin}}
#* {{jêder-pirtûk|ku|jêgirtin=Nezer ji mala dewatê der tê û di riya xwe de '''diçe'''.|nivîskar=Têmûrê Xelîl Mûradov|sernav=Nezerê Mêrxas|sal=1937|rûpel=}}
#* {{jêder|ku|jêgirtin=Gor gotina apê min, ew '''terin''' di mêrga Şêx da gihê diçinin.|İkram Oğuz, ''Jîyan bi kurdî xweş e'', '''2022''', Weşanên NavKurd, r.187}}
#* {{jêder|ku|jêgirtin=Jinikê digote xelkê: Ev mêrê min Boẍoçêf e. Berê xwe dide Pêtro û digot-ê: Boẍoçêf, tu '''çûyî''' kû; ez û zarowên xwe tazî û birçî hiştin...|{{w|Ronahî}}, hj.25, '''1944''', Boẍoçef an Petroyê siseyan}}
# [[hinda|Hinda]] bûn, [[neman]], [[berze bûn]], [[rabûn]].
#* {{jêder-websîte|ku|jêgirtin=Xelk pir kenî bi me tama<br>Genim '''çû''' ewîr û ka ma<br>Têvedan kevirê dama<br>Me derda û wan vexwarî|nivîskar=Mihemedê Cezawîr|url=https://www.xelat.org/welato-2201|malper=xelat.org|sernav=Welato (helbest)|sal=2014}}
==== Ji wêjeya klasîk ====
* {{J:Mela Hisênê Bateyî:Mewlûda Kurdî|jêgirtin='''Çûn''' ji behrê me'rîfet ew hate der<br>[[qetre|Qetre]] jêr werya bi ilham ew qeder}}
* {{J:Feqiyê Teyran|jêgirtin=Taziyê uryan im ez<br>Perde '''çû''' ez mame tazî<br>Dil sera mehbûb dixwazî}}
==== Bi alfabeyên din ====
* {{ku-ar|چوون}}
* {{ku-kr|ч'ӧн}}
==== Formên alternatîv ====
* {{g|ku|çûndin}}
* {{g|ku|çûyin}}
* {{g|ku|çûyîn}}
==== Jê ====
{{kol3|ku
| çûn anîn
| çûnanîn
| çûyî
| derçûn
| jê çûn
| ji ber çûn
| ji bîrê çûn
| ji dest çûn
| jihev çûn
| pev çûn
}}
==== Têkildar ====
{{kol3|ku
| bezîn
| gerîn
| livîn
| meşîn
}}
==== Etîmolojî ====
Ji {{ji|ine-pro|ku|*ḱey-}}. Guherîna ''*ḱ''-ya [[proto-hindûewropî]] bi /ç/ di zimanên [[hindûîranî]] de diyardeyeke berbelav e, binêrin bo nimûne: ''[[çar]], [[çav]], [[çend]], [[çi]]''...
Hevreha {{hv|ckb|چوون}}, [[kurdiya başûrî]] ''[[çûn]]'', [[şêxbizinî]] ''[[cigin]]'', zazakî ''[[şiyayene]]'', goranî/hewramî ''[[şiyay]]'', osetî [[цӕуын]] (çæwyn), {{hv|fa|شدن|tr=berê /şuden/ bi wateya ''çûn'', niha /şoden/ bi manaya ''bûn, bûn tiştek''}}, tacikî [[шудан]] (şudan), {{hv|pal||şuten|sc=Latn}}, hexamenişî [[𐎠𐏁𐎡𐎹𐎺𐎶]] (š(i)yav-, /şiyev/), peştûyî {{g|ps|شول|tr=şwəl}}, {{hv|ae||ş(ii)aw-|sc=Latn}} (/şyew-/), {{hv|sa|च्य्अवते}} (cyávate: [[ketin]]), {{hv|grc|σεύω}} (seúō: [[dan pey]], li [[pey]] çûn; [[lezîn]]) û [[κῐ́ω]] (kíō: çûn) û [[κῑνέω]] (kīnéō: [[livandin]]), latînî ''[[cieō]]'' ([[livandin]]), albanî ''[[qoj]]'' (bixwîne: /çoy/, [[hişyar]] kirin)... Bêjeka kurdî ketiye ermenî wek {{hv|xcl|չուեմ|w=çûn}} û {{hv|hy|չու|w=rêwîtî}}, {{q|hy|չվել|w=bar kirin, koç kirin}}.
Peyva {{hv|grc|κῑνέω}} (kīnéō: [[livandin]]) wek [[kîne-]] û [[sîne-]] ketiye gelek zimanan û kurdî jî, bo nimûne: ''[[kînetîk]], [[sînema]]''.
;{{biçûk|Çavkanî:
* {{Cheung}}
* {{Chyet}}
* {{Lubotsky}}
* {{Tsabolov}}
}}
==== Bi zaravayên din ====
* {{Z|ckb}}: {{W-|ckb|چوون}}
* {{Z|zza}}: {{W-|zza|şîyayene}}
==== Werger ====
{{werger-ser|dûr ketin}}
* {{Z|de}}: {{W+|de|gehen}}, {{W+|de|fahren}}
* {{Z|az}}: {{W+|az|getmək}}
* {{Z|ba}}: {{W-|ba|барыу}}
* {{Z|br}}: {{W+|br|mont}}, {{W+|br|monet}}
* {{Z|bg}}: {{W+|bg|отивам|n}}
* {{Z|cs}}: {{W+|cs|jít}}
* {{Z|zh}}: {{W+|zh|去|tr=qǜ}}
* {{Z|da}}: {{W+|da|gå}}
* {{Z|ar}}: {{W+|ar|راح|tr=raḧe|cuda=راحَ}}, {{W+|ar|ذهب|tr=ḏehebe}}
* {{Z|eo}}: {{W+|eo|iri}}
* {{Z|fa}}: {{W+|fa|رفتن|tr=reften}}
* {{Z|fi}}: {{W+|fi|mennä}}, {{W+|fi|lähteä}}
* {{Z|vls}}: {{W-|vls|goan}}
* {{Z|fr}}: {{W+|fr|aller}}, {{W+|fr|partir}}
* {{Z|gag}}: {{W-|gag|gitmää}}
* {{Z|roa-gal}}: {{W-|roa-gal|aler}}, {{W-|roa-gal|alae}}
* {{Z|got}}: {{W-|got|𐌲𐌰𐌲𐌲𐌰𐌽|tr=gaggan}}
* {{Z|en}}: {{W+|en|go}}
* {{Z|ga}}: {{W-|ga|dul}}
* {{Z|it}}: {{W+|it|andare}}
* {{Z|ja}}: {{W+|ja|行く|tr=iku}}
* {{Z|krc}}: {{W-|krc|барыргъа}}
* {{Z|ca}}: {{W+|ca|anar}}
* {{Z|ky}}: {{W+|ky|баруу}}
* {{Z|ki}}: {{W-|ki|thie}}
* {{Z|ko}}: {{W+|ko|가다|tr=gada}}
* {{Z|kum}}: {{W-|kum|бармакъ}}
* {{Z|lb}}: {{W+|lb|goen}}
* {{Z|mk}}: {{W-|mk|оди|tr=ódi}}
* {{Z|mi}}: {{W-|mi|haere}}
* {{Z|hu}}: {{W+|hu|megy}}
* {{Z|nrf}}: {{W-|nrf|marchi}}
* {{Z|oc}}: {{W+|oc|anar}}
* {{Z|oia}}: {{W-|oia|mara}}
* {{Z|pt}}: {{W+|pt|ir}}
* {{Z|kk}}: {{W+|kk|бару|tr=baruw}}
* {{Z|ro}}: {{W+|ro|merge}}, se {{W+|ro|duce}}
* {{Z|se}}: {{W-|se|mannat}}, {{W-|se|johtit}}, {{W-|se|fitnat}}
* {{Z|sh}}: {{W+|sh|ići}}, {{W-|sh|ићи}}
* {{Z|sk}}: {{W+|sk|ísť}}
* {{Z|es}}: {{W+|es|ir}}
* {{Z|sv}}: {{W+|sv|gå}}, {{W+|sv|åka}}
* {{Z|zdj}}: {{W-|zdj|hwenɗa}}, {{W-|zdj|henɗa}}
* {{Z|tt}}: {{W-|tt|бару}}
* {{Z|tr}}: {{W+|tr|gitmek}}
* {{Z|tk}}: {{W-|tk|gitmek}}
* {{Z|vi}}: {{W+|vi|đi}}
* {{Z|kjh}}: {{W-|kjh|парарға}}
* {{Z|sah}}: {{W-|sah|бар}}
* {{Z|el}}: {{W+|el|πάω|tr=páo}}, {{W+|el|πηγαίνω|tr=piyéno}}
{{werger-bin}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[birêketin]], [[dûrketin]], [[dûr]]bûn, [[royîştin]], [[rewtin]], [[sefer]]kirin
#: {{mk|ku|'''Çûna''' we bixêr be!|Hûn bixêr biçin!|rêz=1}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Bi zaravayên din ====
* {{Z|zza}}: {{W-|zza|şiyayiş}}
==== Werger ====
{{werger-ser}}
* {{Z|tr}}: {{W+|tr|gitme}}
{{werger-bin}}
{{bend}}
=== Hoker ===
{{hoker|ku}}
# [[wek|Wek]], [[mîna]], [[şibî]], [[nola]], [[fena]] (li [[pey]] peyvan bi kar tê).
#: {{mk|ku|Mirov '''çûn'''.|Wek mirovan.|rêz=1}}
==== Etîmolojî ====
Têkilî {{q|ku|çawa}}.
==== Werger ====
{{werger-ser}}
{{werger-bin}}
== {{ziman|ckb}} ==
=== Lêker ===
{{lêker|ckb|sc=Latn}}
# {{ckb-Arab|چوون}}
e4ky608i6dmnacag5sdt1xfp4vv429q
6535030
6535029
2026-09-20T21:43:27Z
~2026-50815-48
15385
Kurmancî/Lêker: beşa «Resim» hate nûkirin (DefSideBoxes)
6535030
wikitext
text/x-wiki
{{h-bnr|cun|cûn|cün|çun|çûn|çün}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/t͡ʃʰuːn/}}
* {{deng|ku|LL-Q36368 (kur)-Dildadil-çûn.wav|dever=Amed}}
* {{deng|ku|LL-Q36163 (kmr)-Ebulfîda-çûn.wav|Deng|dever=Bidlîs}}
* {{ku-kîte}}
=== Lêker ===
{{ku-tewîn-lk|çûn|form=negerguhêz|niha=her-er-r|niha2=ç|niha3=her|borî=çû}}
[[Wêne:Elephant_Walking_animated.gif|thumb|Fîlek bi rê ve '''dihere'''/'''diçe'''/'''tere'''.]]
{{lêker|ku|c=x-ngh}}
# Ji [[der]]ekê [[dûr ketin]], [[cih]]ek [[bi cih hêlan]] / [[bi cih hiştin]], [[dûr]]î derekê bûn.
#: {{hevmane|ku|herîn|rewtin|bi rê ketin}}
#: {{dijmane|ku|hatin}}
#* {{jêder-pirtûk|ku|jêgirtin=Nezer ji mala dewatê der tê û di riya xwe de '''diçe'''.|nivîskar=Têmûrê Xelîl Mûradov|sernav=Nezerê Mêrxas|sal=1937|rûpel=}}
#* {{jêder|ku|jêgirtin=Gor gotina apê min, ew '''terin''' di mêrga Şêx da gihê diçinin.|İkram Oğuz, ''Jîyan bi kurdî xweş e'', '''2022''', Weşanên NavKurd, r.187}}
#* {{jêder|ku|jêgirtin=Jinikê digote xelkê: Ev mêrê min Boẍoçêf e. Berê xwe dide Pêtro û digot-ê: Boẍoçêf, tu '''çûyî''' kû; ez û zarowên xwe tazî û birçî hiştin...|{{w|Ronahî}}, hj.25, '''1944''', Boẍoçef an Petroyê siseyan}}
# [[hinda|Hinda]] bûn, [[neman]], [[berze bûn]], [[rabûn]].
#* {{jêder-websîte|ku|jêgirtin=Xelk pir kenî bi me tama<br>Genim '''çû''' ewîr û ka ma<br>Têvedan kevirê dama<br>Me derda û wan vexwarî|nivîskar=Mihemedê Cezawîr|url=https://www.xelat.org/welato-2201|malper=xelat.org|sernav=Welato (helbest)|sal=2014}}
==== Ji wêjeya klasîk ====
* {{J:Mela Hisênê Bateyî:Mewlûda Kurdî|jêgirtin='''Çûn''' ji behrê me'rîfet ew hate der<br>[[qetre|Qetre]] jêr werya bi ilham ew qeder}}
* {{J:Feqiyê Teyran|jêgirtin=Taziyê uryan im ez<br>Perde '''çû''' ez mame tazî<br>Dil sera mehbûb dixwazî}}
==== Bi alfabeyên din ====
* {{ku-ar|چوون}}
* {{ku-kr|ч'ӧн}}
==== Formên alternatîv ====
* {{g|ku|çûndin}}
* {{g|ku|çûyin}}
* {{g|ku|çûyîn}}
==== Jê ====
{{kol3|ku
| çûn anîn
| çûnanîn
| çûyî
| derçûn
| jê çûn
| ji ber çûn
| ji bîrê çûn
| ji dest çûn
| jihev çûn
| pev çûn
}}
==== Têkildar ====
{{kol3|ku
| bezîn
| gerîn
| livîn
| meşîn
}}
==== Etîmolojî ====
Ji {{ji|ine-pro|ku|*ḱey-}}. Guherîna ''*ḱ''-ya [[proto-hindûewropî]] bi /ç/ di zimanên [[hindûîranî]] de diyardeyeke berbelav e, binêrin bo nimûne: ''[[çar]], [[çav]], [[çend]], [[çi]]''...
Hevreha {{hv|ckb|چوون}}, [[kurdiya başûrî]] ''[[çûn]]'', [[şêxbizinî]] ''[[cigin]]'', zazakî ''[[şiyayene]]'', goranî/hewramî ''[[şiyay]]'', osetî [[цӕуын]] (çæwyn), {{hv|fa|شدن|tr=berê /şuden/ bi wateya ''çûn'', niha /şoden/ bi manaya ''bûn, bûn tiştek''}}, tacikî [[шудан]] (şudan), {{hv|pal||şuten|sc=Latn}}, hexamenişî [[𐎠𐏁𐎡𐎹𐎺𐎶]] (š(i)yav-, /şiyev/), peştûyî {{g|ps|شول|tr=şwəl}}, {{hv|ae||ş(ii)aw-|sc=Latn}} (/şyew-/), {{hv|sa|च्य्अवते}} (cyávate: [[ketin]]), {{hv|grc|σεύω}} (seúō: [[dan pey]], li [[pey]] çûn; [[lezîn]]) û [[κῐ́ω]] (kíō: çûn) û [[κῑνέω]] (kīnéō: [[livandin]]), latînî ''[[cieō]]'' ([[livandin]]), albanî ''[[qoj]]'' (bixwîne: /çoy/, [[hişyar]] kirin)... Bêjeka kurdî ketiye ermenî wek {{hv|xcl|չուեմ|w=çûn}} û {{hv|hy|չու|w=rêwîtî}}, {{q|hy|չվել|w=bar kirin, koç kirin}}.
Peyva {{hv|grc|κῑνέω}} (kīnéō: [[livandin]]) wek [[kîne-]] û [[sîne-]] ketiye gelek zimanan û kurdî jî, bo nimûne: ''[[kînetîk]], [[sînema]]''.
;{{biçûk|Çavkanî:
* {{Cheung}}
* {{Chyet}}
* {{Lubotsky}}
* {{Tsabolov}}
}}
==== Bi zaravayên din ====
* {{Z|ckb}}: {{W-|ckb|چوون}}
* {{Z|zza}}: {{W-|zza|şîyayene}}
==== Werger ====
{{werger-ser|dûr ketin}}
* {{Z|de}}: {{W+|de|gehen}}, {{W+|de|fahren}}
* {{Z|az}}: {{W+|az|getmək}}
* {{Z|ba}}: {{W-|ba|барыу}}
* {{Z|br}}: {{W+|br|mont}}, {{W+|br|monet}}
* {{Z|bg}}: {{W+|bg|отивам|n}}
* {{Z|cs}}: {{W+|cs|jít}}
* {{Z|zh}}: {{W+|zh|去|tr=qǜ}}
* {{Z|da}}: {{W+|da|gå}}
* {{Z|ar}}: {{W+|ar|راح|tr=raḧe|cuda=راحَ}}, {{W+|ar|ذهب|tr=ḏehebe}}
* {{Z|eo}}: {{W+|eo|iri}}
* {{Z|fa}}: {{W+|fa|رفتن|tr=reften}}
* {{Z|fi}}: {{W+|fi|mennä}}, {{W+|fi|lähteä}}
* {{Z|vls}}: {{W-|vls|goan}}
* {{Z|fr}}: {{W+|fr|aller}}, {{W+|fr|partir}}
* {{Z|gag}}: {{W-|gag|gitmää}}
* {{Z|roa-gal}}: {{W-|roa-gal|aler}}, {{W-|roa-gal|alae}}
* {{Z|got}}: {{W-|got|𐌲𐌰𐌲𐌲𐌰𐌽|tr=gaggan}}
* {{Z|en}}: {{W+|en|go}}
* {{Z|ga}}: {{W-|ga|dul}}
* {{Z|it}}: {{W+|it|andare}}
* {{Z|ja}}: {{W+|ja|行く|tr=iku}}
* {{Z|krc}}: {{W-|krc|барыргъа}}
* {{Z|ca}}: {{W+|ca|anar}}
* {{Z|ky}}: {{W+|ky|баруу}}
* {{Z|ki}}: {{W-|ki|thie}}
* {{Z|ko}}: {{W+|ko|가다|tr=gada}}
* {{Z|kum}}: {{W-|kum|бармакъ}}
* {{Z|lb}}: {{W+|lb|goen}}
* {{Z|mk}}: {{W-|mk|оди|tr=ódi}}
* {{Z|mi}}: {{W-|mi|haere}}
* {{Z|hu}}: {{W+|hu|megy}}
* {{Z|nrf}}: {{W-|nrf|marchi}}
* {{Z|oc}}: {{W+|oc|anar}}
* {{Z|oia}}: {{W-|oia|mara}}
* {{Z|pt}}: {{W+|pt|ir}}
* {{Z|kk}}: {{W+|kk|бару|tr=baruw}}
* {{Z|ro}}: {{W+|ro|merge}}, se {{W+|ro|duce}}
* {{Z|se}}: {{W-|se|mannat}}, {{W-|se|johtit}}, {{W-|se|fitnat}}
* {{Z|sh}}: {{W+|sh|ići}}, {{W-|sh|ићи}}
* {{Z|sk}}: {{W+|sk|ísť}}
* {{Z|es}}: {{W+|es|ir}}
* {{Z|sv}}: {{W+|sv|gå}}, {{W+|sv|åka}}
* {{Z|zdj}}: {{W-|zdj|hwenɗa}}, {{W-|zdj|henɗa}}
* {{Z|tt}}: {{W-|tt|бару}}
* {{Z|tr}}: {{W+|tr|gitmek}}
* {{Z|tk}}: {{W-|tk|gitmek}}
* {{Z|vi}}: {{W+|vi|đi}}
* {{Z|kjh}}: {{W-|kjh|парарға}}
* {{Z|sah}}: {{W-|sah|бар}}
* {{Z|el}}: {{W+|el|πάω|tr=páo}}, {{W+|el|πηγαίνω|tr=piyéno}}
{{werger-bin}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[birêketin]], [[dûrketin]], [[dûr]]bûn, [[royîştin]], [[rewtin]], [[sefer]]kirin
#: {{mk|ku|'''Çûna''' we bixêr be!|Hûn bixêr biçin!|rêz=1}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Bi zaravayên din ====
* {{Z|zza}}: {{W-|zza|şiyayiş}}
==== Werger ====
{{werger-ser}}
* {{Z|tr}}: {{W+|tr|gitme}}
{{werger-bin}}
{{bend}}
=== Hoker ===
{{hoker|ku}}
# [[wek|Wek]], [[mîna]], [[şibî]], [[nola]], [[fena]] (li [[pey]] peyvan bi kar tê).
#: {{mk|ku|Mirov '''çûn'''.|Wek mirovan.|rêz=1}}
==== Etîmolojî ====
Têkilî {{q|ku|çawa}}.
==== Werger ====
{{werger-ser}}
{{werger-bin}}
== {{ziman|ckb}} ==
=== Lêker ===
{{lêker|ckb|sc=Latn}}
# {{ckb-Arab|چوون}}
iwx7qczy6ygeyeviz31d0m7toilpq69
sînor
0
4960
6534896
6356873
2026-09-20T13:39:57Z
Wikihez
7344
Kurmancî/Navdêr: mane hate guhartin, mane, hevmane hatin guhartin (DefSideBoxes)
6534896
wikitext
text/x-wiki
{{ds|sînor|sinor}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=n}}
# [[xet|Xet]]a ku du [[der]]an ji hev cuda dike (bi taybetî du [[dewlet]]an yan du [[zevî|zeviyan]]).
#: {{hevmane|ku|tixûb|hidûd|kewşen}}
#: {{nm|ku|li ser '''sînorê''' Îranê û Iraqê.}}
# [[cih|Cih]]ê yan [[nuqte]]ya herî [[dûr]] ya ku [[destûr]] heye mirov biçiyê yan bike.
#: {{nm|ku|Divê '''sînorek''' bo [[ceng]]ê bêt danîn.|Nabe hema ceng [[çawa]] be, bêt [[qebûlkirin]].}}
==== Tewandin ====
{{ku-tewîn-nav/nû|nêr}}
==== Formên alternatîv ====
{{kol3|ku
|sinir
|sinor
|sîndor
}}
==== Jê ====
{{kol3|ku|cure=Jê
|bêsînor
|bêsînorî
|bisînor
|bisînorî
|sersînor
|sersînorî
|sînordar
|sînordarî
|sînor girtin<q:lêker>
|sînor vekirin<q:lêker>
|sînor dan
|sînor kirin
|sînor danîn
|sînorkirin<q:navdêr>
|sînordan<q:navdêr>
|sînordanîn
|sînorkirî<q:rengdêr>
}}
==== Etîmolojî ====
Ji {{ji|el|ku|σύνορο|w=sinor}}, ji {{ji|gkm|ku|σῠ́νορον|w=sinor, [[tixûb]], [[lîmît]]}} ji {{ji|grc-koi|ku|σῠ́νορος|w=pê re [[cîranbûn]]}}: {{g|grc|σῠ́ν|w=pê re}} + {{g|grc|ὅρος|w=tixûb}}.
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W+|af|grens}}
* {{Z|sq}}: {{W+|sq|kufi}}
* {{Z|de}}: {{W+|de|Grenze}} {{mê}}, {{W+|de|Schranke}} {{mê}}, {{W+|de|Abgrenzung}} {{mê}}, {{W+|de|Borte}} {{mê}}, {{W+|de|Grenzlinie}} {{mê}}, {{W+|de|Leiste}} {{mê}}, {{W-|de|Trennungslinie}} {{mê}}, {{W-|de|Deckelung|f|guman=erê}}, {{W+|de|Einschränkung|f|guman=erê}}, {{W+|de|Scheide|f|guman=erê}}
* {{Z|da}}: {{W+|da|grænse}}
* {{Z|ar}}: {{W-|ar|حدود}}, {{W+|ar|حد}}, {{W-|ar|فاصل}}, {{W-|ar|عازل}}
* {{Z|eo}}: {{W+|eo|limo}}, {{W-|eo|landlimo}}
* {{Z|fa}}: {{W+|fa|مرز}}, {{W+|fa|حد}}, {{W+|fa|لبه}}, {{W+|fa|محدوده}}
* {{Z|fo}}: {{W+|fo|mark}}
* {{Z|fi}}: {{W+|fi|raja}}
* {{Z|fr}}: {{W+|fr|frontière}}, {{W+|fr|limite}}
* {{Z|fy}}: {{W-|fy|grins}}
* {{Z|nl}}: {{W+|nl|grens}}, {{W+|nl|perk}}, {{W+|nl|landsgrens}}
* {{Z|he}}: {{W+|he|גבול|cuda=גְּבול}}
* {{Z|en}}: {{W+|en|border}}, {{W+|en|boundary}}, {{W+|en|frontier}}, {{W+|en|limit}}, {{W+|en|end}}, {{W+|en|perimeter}}
* {{Z|it}}: {{W+|it|frontiera}} {{mê}}, {{W+|it|confine}}
* {{Z|ca}}: {{W+|ca|frontera}}, {{W+|ca|límit}}
* {{Z|la}}: {{W+|la|finis}}, {{W+|la|limes}}, {{W+|la|ora}}
* {{Z|hu}}: {{W+|hu|határ}}
* {{Z|no}}: {{W+|no|grense}}
* {{Z|pap}}: {{W-|pap|frontera}}, {{W-|pap|limite}}
* {{Z|pt}}: {{W-|pt|confins}}, {{W+|pt|fronteira}}, {{W+|pt|limite}}, {{W+|pt|raia}}
* {{Z|ru}}: {{W+|ru|граница}}
* {{Z|es}}: {{W+|es|frontera}} {{mê}}, {{W+|es|límite}}
* {{Z|sv}}: {{W+|sv|gräns}}
* {{Z|tr}}: {{W+|tr|sınır}}, {{W+|tr|hudut}}, {{W+|tr|nokta}}, {{W+|tr|had}}, {{W+|tr|tahdit}}, {{W+|tr|çizgi}}
* {{Z|el}}: {{W+|el|σύνορο}}, {{W+|el|μεθόριος}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=înors|sorîn}}
4k11uditw6mve92vou8q7fuqu86r8es
6534899
6534896
2026-09-20T14:05:00Z
Wikihez
7344
Kurmancî/Navdêr: beşa «Etîmolojî» hate nûkirin (DefSideBoxes)
6534899
wikitext
text/x-wiki
{{ds|sînor|sinor}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=n}}
# [[xet|Xet]]a ku du [[der]]an ji hev cuda dike (bi taybetî du [[dewlet]]an yan du [[zevî|zeviyan]]).
#: {{hevmane|ku|tixûb|hidûd|kewşen}}
#: {{nm|ku|li ser '''sînorê''' Îranê û Iraqê.}}
# [[cih|Cih]]ê yan [[nuqte]]ya herî [[dûr]] ya ku [[destûr]] heye mirov biçiyê yan bike.
#: {{nm|ku|Divê '''sînorek''' bo [[ceng]]ê bêt danîn.|Nabe hema ceng [[çawa]] be, bêt [[qebûlkirin]].}}
==== Tewandin ====
{{ku-tewîn-nav/nû|nêr}}
==== Formên alternatîv ====
{{kol3|ku
|sinir
|sinor
|sîndor
}}
==== Jê ====
{{kol3|ku|cure=Jê
|bêsînor
|bêsînorî
|bisînor
|bisînorî
|sersînor
|sersînorî
|sînordar
|sînordarî
|sînor girtin<q:lêker>
|sînor vekirin<q:lêker>
|sînor dan
|sînor kirin
|sînor danîn
|sînorkirin<q:navdêr>
|sînordan<q:navdêr>
|sînordanîn
|sînorkirî<q:rengdêr>
}}
==== Etîmolojî ====
{{etîmon|ku|:dariştî|el:σύνορο<t:sînor><etimon:mîrasmayî<gkm:σῠ́νορον<t:sinor, [[tixûb]], [[lîmît]]><etimon:mîrasmayî<grc-koi:σῠ́νορος<t:pê re [[cîranbûn]]><etimon:qertaf<grc:σῠ́ν<t:pê re>><grc:ὅρος<t:tixûb>>>>>>>|metn=++|dar=1}}
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W+|af|grens}}
* {{Z|sq}}: {{W+|sq|kufi}}
* {{Z|de}}: {{W+|de|Grenze}} {{mê}}, {{W+|de|Schranke}} {{mê}}, {{W+|de|Abgrenzung}} {{mê}}, {{W+|de|Borte}} {{mê}}, {{W+|de|Grenzlinie}} {{mê}}, {{W+|de|Leiste}} {{mê}}, {{W-|de|Trennungslinie}} {{mê}}, {{W-|de|Deckelung|f|guman=erê}}, {{W+|de|Einschränkung|f|guman=erê}}, {{W+|de|Scheide|f|guman=erê}}
* {{Z|da}}: {{W+|da|grænse}}
* {{Z|ar}}: {{W-|ar|حدود}}, {{W+|ar|حد}}, {{W-|ar|فاصل}}, {{W-|ar|عازل}}
* {{Z|eo}}: {{W+|eo|limo}}, {{W-|eo|landlimo}}
* {{Z|fa}}: {{W+|fa|مرز}}, {{W+|fa|حد}}, {{W+|fa|لبه}}, {{W+|fa|محدوده}}
* {{Z|fo}}: {{W+|fo|mark}}
* {{Z|fi}}: {{W+|fi|raja}}
* {{Z|fr}}: {{W+|fr|frontière}}, {{W+|fr|limite}}
* {{Z|fy}}: {{W-|fy|grins}}
* {{Z|nl}}: {{W+|nl|grens}}, {{W+|nl|perk}}, {{W+|nl|landsgrens}}
* {{Z|he}}: {{W+|he|גבול|cuda=גְּבול}}
* {{Z|en}}: {{W+|en|border}}, {{W+|en|boundary}}, {{W+|en|frontier}}, {{W+|en|limit}}, {{W+|en|end}}, {{W+|en|perimeter}}
* {{Z|it}}: {{W+|it|frontiera}} {{mê}}, {{W+|it|confine}}
* {{Z|ca}}: {{W+|ca|frontera}}, {{W+|ca|límit}}
* {{Z|la}}: {{W+|la|finis}}, {{W+|la|limes}}, {{W+|la|ora}}
* {{Z|hu}}: {{W+|hu|határ}}
* {{Z|no}}: {{W+|no|grense}}
* {{Z|pap}}: {{W-|pap|frontera}}, {{W-|pap|limite}}
* {{Z|pt}}: {{W-|pt|confins}}, {{W+|pt|fronteira}}, {{W+|pt|limite}}, {{W+|pt|raia}}
* {{Z|ru}}: {{W+|ru|граница}}
* {{Z|es}}: {{W+|es|frontera}} {{mê}}, {{W+|es|límite}}
* {{Z|sv}}: {{W+|sv|gräns}}
* {{Z|tr}}: {{W+|tr|sınır}}, {{W+|tr|hudut}}, {{W+|tr|nokta}}, {{W+|tr|had}}, {{W+|tr|tahdit}}, {{W+|tr|çizgi}}
* {{Z|el}}: {{W+|el|σύνορο}}, {{W+|el|μεθόριος}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=înors|sorîn}}
4wm6yaizksq692xofxaziel5lw8m1np
6534914
6534899
2026-09-20T15:12:09Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir, {{ku-tewîn-nav/nû}} → {{ku-tewîn-nav}}.)
6534914
wikitext
text/x-wiki
{{ds|sînor|sinor}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=n}}
# [[xet|Xeta]] ku du [[der]]an ji hev cuda dike (bi taybetî du [[dewlet]]an yan du [[zevî|zeviyan]]).
#: {{hevmane|ku|tixûb|hidûd|kewşen}}
#: {{nm|ku|li ser '''sînorê''' Îranê û Iraqê.}}
# [[cih|Cihê]] yan [[nuqte]]ya herî [[dûr]] ya ku [[destûr]] heye mirov biçiyê yan bike.
#: {{nm|ku|Divê '''sînorek''' bo [[ceng]]ê bêt danîn.|Nabe hema ceng [[çawa]] be, bêt [[qebûlkirin]].}}
==== Tewandin ====
{{ku-tewîn-nav|nêr}}
==== Formên alternatîv ====
{{kol3|ku
|sinir
|sinor
|sîndor
}}
==== Jê ====
{{kol3|ku|cure=Jê
|bêsînor
|bêsînorî
|bisînor
|bisînorî
|sersînor
|sersînorî
|sînordar
|sînordarî
|sînor girtin<q:lêker>
|sînor vekirin<q:lêker>
|sînor dan
|sînor kirin
|sînor danîn
|sînorkirin<q:navdêr>
|sînordan<q:navdêr>
|sînordanîn
|sînorkirî<q:rengdêr>
}}
==== Etîmolojî ====
{{etîmon|ku|:dariştî|el:σύνορο<t:sînor><etimon:mîrasmayî<gkm:σῠ́νορον<t:sinor, [[tixûb]], [[lîmît]]><etimon:mîrasmayî<grc-koi:σῠ́νορος<t:pê re [[cîranbûn]]><etimon:qertaf<grc:σῠ́ν<t:pê re>><grc:ὅρος<t:tixûb>>>>>>>|metn=++|dar=1}}
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W+|af|grens}}
* {{Z|sq}}: {{W+|sq|kufi}}
* {{Z|de}}: {{W+|de|Grenze}} {{mê}}, {{W+|de|Schranke}} {{mê}}, {{W+|de|Abgrenzung}} {{mê}}, {{W+|de|Borte}} {{mê}}, {{W+|de|Grenzlinie}} {{mê}}, {{W+|de|Leiste}} {{mê}}, {{W-|de|Trennungslinie}} {{mê}}, {{W-|de|Deckelung|f|guman=erê}}, {{W+|de|Einschränkung|f|guman=erê}}, {{W+|de|Scheide|f|guman=erê}}
* {{Z|da}}: {{W+|da|grænse}}
* {{Z|ar}}: {{W-|ar|حدود}}, {{W+|ar|حد}}, {{W-|ar|فاصل}}, {{W-|ar|عازل}}
* {{Z|eo}}: {{W+|eo|limo}}, {{W-|eo|landlimo}}
* {{Z|fa}}: {{W+|fa|مرز}}, {{W+|fa|حد}}, {{W+|fa|لبه}}, {{W+|fa|محدوده}}
* {{Z|fo}}: {{W+|fo|mark}}
* {{Z|fi}}: {{W+|fi|raja}}
* {{Z|fr}}: {{W+|fr|frontière}}, {{W+|fr|limite}}
* {{Z|fy}}: {{W-|fy|grins}}
* {{Z|nl}}: {{W+|nl|grens}}, {{W+|nl|perk}}, {{W+|nl|landsgrens}}
* {{Z|he}}: {{W+|he|גבול|cuda=גְּבול}}
* {{Z|en}}: {{W+|en|border}}, {{W+|en|boundary}}, {{W+|en|frontier}}, {{W+|en|limit}}, {{W+|en|end}}, {{W+|en|perimeter}}
* {{Z|it}}: {{W+|it|frontiera}} {{mê}}, {{W+|it|confine}}
* {{Z|ca}}: {{W+|ca|frontera}}, {{W+|ca|límit}}
* {{Z|la}}: {{W+|la|finis}}, {{W+|la|limes}}, {{W+|la|ora}}
* {{Z|hu}}: {{W+|hu|határ}}
* {{Z|no}}: {{W+|no|grense}}
* {{Z|pap}}: {{W-|pap|frontera}}, {{W-|pap|limite}}
* {{Z|pt}}: {{W-|pt|confins}}, {{W+|pt|fronteira}}, {{W+|pt|limite}}, {{W+|pt|raia}}
* {{Z|ru}}: {{W+|ru|граница}}
* {{Z|es}}: {{W+|es|frontera}} {{mê}}, {{W+|es|límite}}
* {{Z|sv}}: {{W+|sv|gräns}}
* {{Z|tr}}: {{W+|tr|sınır}}, {{W+|tr|hudut}}, {{W+|tr|nokta}}, {{W+|tr|had}}, {{W+|tr|tahdit}}, {{W+|tr|çizgi}}
* {{Z|el}}: {{W+|el|σύνορο}}, {{W+|el|μεθόριος}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=înors|sorîn}}
8qayxwi40rgrjdphqezmwubaslizc3f
xerîte
0
9699
6534894
6534892
2026-09-20T12:12:06Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Valahiya beşan rast kir, --Valahiyên nehewce, {{ku-tewîn-nav/nû}} → {{ku-tewîn-nav}}.)
6534894
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Worldmap LandAndPolitical.jpg|thumb|250px|'''Xerîteyeke''' dinyayê.]]
{{navdêr|ku|z=m}}
# [[wêne|Wêneyê]] bi [[dest]] yan bi [[kompûter]]ê hatiye çêkirin yê ku [[sinor]]ên [[welat]]an yan [[herêm]]an [[nîşan]] dide.
#: {{hevmane|ku|nexşe}}
#: {{mk|ku|'''Xerîteya''' cîhanê; '''xerîta''' Kurdistanê.}}
#* {{jêder-kitêb|ku|nivîskar=Mehmed Uzun|sernav=Mirina Kalekî Rind|weşanxane=Orfeus|cih=Stockholm|sal=1987|rûpel=48|isbn=978-91-87074-02-8|jêgirtin=Borges di kurte-çîroka xwe «Borges û Ez» de digot; «ez ji saetên qûmê, '''xarîte''', weşanên sedsala hıjdan{{sic}}, tahma qahwe û pexşanên Stevenson hez dikim.»}}
==== Tewandin ====
{{ku-tewîn-nav|mê|guherto=hemû}}
==== Notên bikaranînê ====
{{agahdarî}} Van [[dehe]]yên dawiyê di kurdiya nivîskî de peyva [[nexşe]] wek peyveke esilkurdî tê tercihkirin lê di rastiyê de ew ji peyva [[nexş]]/[[neqş]] e ku bi xwe jî ji erebî ye. Di kurdî de [[nexş]] maneyeke din dide loma "xerîte" çêtir e.
==== Bi alfabeyên din ====
* {{ku-ar|خهریته}}
==== Formên alternatîv ====
{{kol3|ku|cure=Herwiha
|xarît
|xarîta
|xarîte
|xerît
|xerîta
|xerîde
}}
==== Jê ====
{{kol3|ku|cure=Jê
|xerîteçêker
|xerîteçêkerî
|xerîtedaner
|xerîtedanerî
|xerîtenas
|xerîtenasî
|xerîtesaz
|xerîtesazî
|xerîtevan
|xerîtevanî
|xerîtezan
|xerîtezanî
}}
==== Etîmolojî ====
{{etîmon|ku|:deyn|ar:خَرِيطَة|dar=1}}
Ji {{ji|ar|ku|خريطة|tr=xeṛîṭe}} ji {{ji|arc|ku|חרתא|tr=xerta}} ji {{ji|el|ku|χάρτης}} (xartês: 1. perr, kaxez 2. xerîte) ji [[χαράσσω]] (xarasos: [[xîçandin]], [[xîç]] kirin, [[nîgar]] kirin) ji {{ji|ine-pro|ku|-}} *[[ǵʰer-]] ([[xîç]] lê kirin, [[selixandin]]). [[χάρτης]] ya yûnanî bi rêya [[charta]] ya latînî herwiha serekaniya peyvên bi maneya "xerîte" yên gelek zimanên ewropî ye jî: [[carte]] ya fransî [[Karte]] ya almanî, [[karta]] ya swêdî, [[карта]] (karta) ya rusî... Herwiha [[kart]] û [[karton]]ên kurdî jî ji heman peyva yûnanî ne.
==== Werger ====
{{werger-ser}}
* {{Z|ace}}: {{W-|ace|peuta}}
* {{Z|af}}: {{W-|af|kaart}}
* {{Z|ak}}: {{W-|ak|asasemfoni}}
* {{Z|sq}}: {{W+|sq|hartë|m}}
* {{Z|de}}: {{W+|de|Landkarte|m}}, {{W+|de|Karte|m}}, {{W+|de|Plan|n}}
* {{Z|am}}: {{W-|am|ካርታ}}
* {{Z|as}}: {{W-|as|মানচিত্ৰ}}, {{W-|as|নক্সা}}
* {{Z|ast}}: {{W+|ast|mapa|n}}
* {{Z|az}}: {{W+|az|xəritə}}
* {{Z|eu}}: {{W+|eu|mapa}}
* {{Z|ba}}: {{W-|ba|карта}}
* {{Z|be}}: {{W-|be|ка́рта|m}}, {{W-|be|ма́па|m}}, {{W-|be|план|n}}
* {{Z|bn}}: {{W+|bn|মানচিত্র}}, {{W-|bn|নকশা}}
* {{Z|br}}: {{W+|br|kartenn|m}}, {{W+|br|kartoù|p}}
* {{Z|bg}}: {{W+|bg|ка́рта|m}}
* {{Z|my}}: {{W+|my|မြေပုံ}}
* {{Z|cs}}: {{W+|cs|mapa|m}}
* {{Z|chr}}: {{W-|chr|ᎡᎶᎯ ᏓᏟᎶᏍᏔᏅ}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|地圖|tr=dei6 tou4}}
*: {{Z|dng}}: {{W-|dng|дитў}}
*: {{Z|hak}}: {{W-|hak|地圖|tr=thi-thù}}
*: {{Z|cmn}}: {{W+|cmn|地圖|tr=dìtú}}
*: {{Z|cdo}}: {{W-|cdo|地圖|tr=dê-dù}}
*: {{Z|nan}}: {{W+|nan|地圖|tr=tē-tô͘ / toē-tô͘}}
*: {{Z|wuu}}: {{W-|wuu|地圖}}
* {{Z|ckt}}: {{W-|ckt|нутэкэԓикэԓ|tr=nutėkėḷikėḷ}}
* {{Z|da}}: {{W+|da|kort|nt}}
* {{Z|id}}: {{W+|id|peta}}, {{W+|id|map}}, {{W+|id|atlas}}
* {{Z|ar}}: {{W+|ar|خَرِيطَة|m}}
*: {{Z|arz}}: {{W-|arz|خريطة|m|tr=ḵarīṭa}}
* {{Z|hy}}: {{W+|hy|քարտեզ}}
* {{Z|myv}}: {{W-|myv|модарт}}
* {{Z|eo}}: {{W+|eo|mapo}}
* {{Z|et}}: {{W-|et|kaart}}
* {{Z|fa}}: {{W+|fa|نقشه|tr=naqše}}, {{W+|fa|خریطه|tr=xarite}}
* {{Z|fo}}: {{W-|fo|kort|nt}}
* {{Z|fj}}: {{W-|fj|mape}}
* {{Z|fi}}: {{W+|fi|kartta}}
* {{Z|fr}}: {{W+|fr|carte|m}}, {{W+|fr|plan|n}}
* {{Z|fur}}: {{W-|fur|mape}}
* {{Z|gd}}: {{W-|gd|map|n}}, {{W-|gd|mapa|n}}, {{W-|gd|cairt-iùil|n}}
* {{Z|gl}}: {{W+|gl|mapa|n}}
* {{Z|gu}}: {{W-|gu|નકશા|n}}
* {{Z|ka}}: {{W-|ka|რუკა}}
* {{Z|gn}}: {{W-|gn|yvyra’anga}}
* {{Z|ht}}: {{W-|ht|kat}}
* {{Z|haw}}: {{W-|haw|palapalaʻāina}}
* {{Z|hi}}: {{W-|hi|नक़्शा|n}}/{{W+|hi|नक्शा|n}}, {{W+|hi|मानचित्र|n}}
* {{Z|nl}}: {{W+|nl|kaart|m}}, {{W+|nl|landkaart|m}}, {{W+|nl|plan|nt}}, {{W+|nl|plattegrond|n}}
* {{Z|he}}: {{W+|he|מַפָּה|m|tr=mapá}}
* {{Z|io}}: {{W+|io|mapo}}
* {{Z|ilo}}: {{W-|ilo|mápa}}
* {{Z|en}}: {{W+|en|map}}
* {{Z|iu}}: {{W-|iu|nunaŋŋuaq}}
* {{Z|ga}}: {{W+|ga|léarscáil|m}}, {{W-|ga|mapa|n}}
* {{Z|it}}: {{W+|it|carta|m}}, {{W+|it|mappa|m}}, {{W-|it|carta geografica|m}}, {{W+|it|cartina|m}}
* {{Z|is}}: {{W+|is|kort|nt}}
* {{Z|ja}}: {{W+|ja|地図|tr=ちず, chizu}}
* {{Z|kl}}: {{W-|kl|nunap assinga}}
* {{Z|kn}}: {{W+|kn|ನಕ್ಷೆ}}, {{W+|kn|ಭೂಪಟ}}
* {{Z|csb}}: {{W-|csb|kôrta|m}}
* {{Z|ca}}: {{W+|ca|mapa|n}}, {{W+|ca|plànol|n}}
* {{Z|ky}}: {{W+|ky|карта}}
* {{Z|si}}: {{W+|si|සිතියම}}
* {{Z|ko}}: {{W+|ko|지도}}
* {{Z|kw}}: {{W-|kw|mappa|n}}
* {{Z|la}}: {{W-|la|charta|m}}, {{W-|la|tabula|m}}, {{W-|la|itinerārium|nt}}
* {{Z|lv}}: {{W-|lv|karte|m}}
* {{Z|lo}}: {{W+|lo|ແຜນທີ່}}
* {{Z|li}}: {{W+|li|kaart|m}}
* {{Z|lt}}: {{W+|lt|žemėlapis|n}}
* {{Z|liv}}: {{W-|liv|kōrtõz}}
* {{Z|khb}}: {{W-|khb|ᦔᦳᧅᧈᦵᦙᦲᧂ}}
* {{Z|lb}}: {{W-|lb|Landkaart|m}}
* {{Z|mk}}: {{W-|mk|карта|m}}, {{W-|mk|план|n}}
* {{Z|ml}}: {{W+|ml|ഭൂപടം}}
* {{Z|ms}}: {{W+|ms|peta}}
* {{Z|mt}}: {{W-|mt|mappa|m}}
* {{Z|gv}}: {{W-|gv|caslys|n}}
* {{Z|mi}}: {{W-|mi|mapi}}
* {{Z|mr}}: {{W-|mr|नकाशा|n}}
* {{Z|hu}}: {{W+|hu|térkép}}
* {{Z|mn}}:
*: Kirîlî: {{W-|mn|газрын зураг}}
* {{Z|mnw}}: {{W-|mnw|ဗီုတိ|tr=pɜ̀m tɔeˀ}}
* {{Z|moe}}: {{W-|moe|assiu-mashinaikan}}
* {{Z|nv}}: {{W-|nv|kéyah beʼelyaaígíí|sc=Latn}}, {{W-|nv|atiin bikááʼ naashchʼąąʼígíí|sc=Latn}}
* {{Z|na}}: {{W-|na|ekorob}}
* {{Z|ne}}: {{W-|ne|नक्शा}}
* {{Z|no}}:
*: {{Z|nb}}: {{W+|nb|kart|nt}}
*: {{Z|nn}}: {{W-|nn|kart|nt}}
* {{Z|oj}}: {{W-|oj|akii-mazinaʼigan}}
* {{Z|oc}}: {{W+|oc|mapa|m}}, {{W+|oc|carta|m}}
* {{Z|or}}: {{W+|or|ମାନଚିତ୍ର}}
* {{Z|om}}: {{W-|om|maappii}}
* {{Z|ota}}: {{W-|ota|خریطه|tr=harita}}
* {{Z|ug}}: {{W-|ug|خەرىتە}}
* {{Z|uz}}: {{W+|uz|xarita}}, {{W+|uz|karta}}
* {{Z|pap}}: {{W-|pap|mapa}}
* {{Z|ps}}: {{W-|ps|خريطه|m|tr=xaritá}}, {{W+|ps|نخشه|m|tr=naxšá}}, {{W+|ps|نخچه|m|tr=naxčá}}, {{W+|ps|نقشه|m|tr=naqšá}}
* {{Z|pdt}}: {{W-|pdt|Launtkoat|m}}, {{W-|pdt|Koat|m}}
* {{Z|pl}}: {{W+|pl|mapa|m}}
* {{Z|pt}}: {{W+|pt|mapa|n}}, {{W+|pt|carta|m}}, {{W-|pt|carta geográfica|m}}
* {{Z|pa}}: {{W-|pa|ਨਕਸ਼ਾ|n}}
* {{Z|kk}}: {{W-|kk|карта}}
* {{Z|rm}}: {{W-|rm|carta|m}}
* {{Z|ro}}: {{W+|ro|hartă|m}}
* {{Z|ru}}: {{W+|ru|ка́рта|m}}, {{W+|ru|план|n}}
* {{Z|rue}}: {{W-|rue|ма́па|m}}
* {{Z|se}}: {{W-|se|gárta}}, {{W-|se|kárta}}
* {{Z|sa}}: {{W-|sa|मानचित्र|nt}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|ка̑рта|m}}, {{W-|sh|ма̏па|m}}, {{W-|sh|зе̏мљовӣд|n}}
*: Latînî: {{W+|sh|kȃrta|m}}, {{W+|sh|mȁpa|m}}, {{W+|sh|zȅmljovīd|n}}
* {{Z|sco}}: {{W-|sco|cairt}}
* {{Z|sk}}: {{W+|sk|mapa|m}}, {{W-|sk|plán|n}}
* {{Z|sl}}: {{W+|sl|zemljevid|n}}, {{W+|sl|karta|m}}
* {{Z|so}}: {{W-|so|khariiddo|m}}, {{W-|so|khariidad|m}}
* Sorbî:
*: {{Z|dsb}}: {{W-|dsb|kórta|m}}
* {{Z|st}}: {{W-|st|mmapa|c9|c10}}
* {{Z|es}}: {{W+|es|mapa|n}}, {{W+|es|plano|n}}, {{W+|es|carta|m}}
* {{Z|sw}}: {{W+|sw|ramani|c9|c10}}
* {{Z|sv}}: {{W+|sv|karta|g}}
* {{Z|shp}}: {{W-|shp|mápa}}
* {{Z|tg}}: {{W+|tg|харита}}, {{W-|tg|нақша}}
* {{Z|tl}}: {{W+|tl|mapa}}
* {{Z|ta}}: {{W+|ta|நிலப்படம்}}, {{W+|ta|வரைபடம்}}
* {{Z|th}}: {{W+|th|แผนที่}}
* {{Z|te}}: {{W+|te|పటము}}
* {{Z|crh}}: {{W-|crh|harita}}
* {{Z|tt}}: {{W+|tt|карта}}, {{W-|tt|харита}}
* {{Z|ti}}: {{W-|ti|ካርታ}}
* {{Z|tr}}: {{W+|tr|harita}}
* {{Z|tk}}: {{W-|tk|karta}}
* {{Z|bo}}: {{W-|bo|ས་བཀྲ}}
* {{Z|ts}}: {{W-|ts|mepe}}
* {{Z|ur}}: {{W-|ur|نقشہ|n|tr=nakśā}}, {{W-|ur|ماںچتر|n|tr=māncitra}}, {{W-|ur|خریطه|tr=xarīta}}
* {{Z|uk}}: {{W+|uk|ка́рта|m}}, {{W+|uk|ма́па|m}}, {{W-|uk|план|n}}
* {{Z|vec}}: {{W-|vec|mapa}}
* {{Z|vi}}: {{W+|vi|bản đồ}}, {{W+|vi|địa đồ}}
* {{Z|wa}}: {{W-|wa|siiwal}}, {{W+|wa|mape|m}}
* {{Z|war}}: {{W-|war|mapa}}
* {{Z|cy}}: {{W+|cy|map|n}}
* {{Z|wym}}: {{W-|wym|łaondmaop}}
* {{Z|km}}: {{W+|km|ផែនទី}}
* {{Z|xh}}: {{W-|xh|imephu}}
* {{Z|sah}}: {{W-|sah|хаарта}}
* {{Z|yi}}: {{W-|yi|מאַפּע|m}}, {{W-|yi|קאַרטע|m}}, {{W-|yi|פּלאַן|n}}
* {{Z|yo}}: {{W-|yo|máàpù}}
* {{Z|el}}: {{W+|el|χάρτης|n}}, {{W+|el|άτλαντας|n}}, {{W+|el|άτλας|n}}
* {{Z|za}}: {{W-|za|deihdoz}}, {{W-|za|diduz}}
* {{Z|zu}}: {{W-|zu|ibalazwe|c5|c6}}
{{werger-bin}}
:{{werger çavkanî|en|map|beş=Ev qismê Wergerê|oldid=75872710}}
==== Anagram ====
* {{anagram|ku|a=eeîrtx|erxetî|exterî|xeterî|xetîre}}
ejlnmeawb437x5hbxom2tvl5x2wjq59
hewşengeh
0
10956
6534931
6308536
2026-09-20T17:55:22Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534931
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[yarîgeh]], [[lîstingeh]], [[lîstikgeh]], [[sahe]], [[stadyûm]], [[arena]], [[meydan]], [[gorepan]], [[com]], [[gom]], [[zom]]
#* {{jêder-websîte|ku|jêgirtin=Gelo dê '''Hewşengeha''' Rhein Energi ya Kolnê were dagirtin an na?|nivîskar=Jîr Dilovan|sernav=Coşa Mîhrîcanê ez mendehoş kirim|websîte=Rojev.com|sal=9/2005}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|hewşanga
|hewşange
|hewşangeh
|hewşangih
|hewşenga
|hewşenge
|hewşengih
}}
==== Jê ====
{{kol3|ku
|hewşengehî
}}
==== Etîmolojî ====
* [[hewş]] + [[-en-]] + [[geh]]
qokfaxzsqq19srh2ulfn06znb0jyc73
tîm
0
11160
6535035
6363145
2026-09-20T23:07:09Z
Wikihez
7344
Kurmancî/Navdêr: beşa «Etîmolojî» hate nûkirin (DefSideBoxes)
6535035
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# Çend [[kes]] yan [[tişt]] yên [[li gel]] [[hev]].
#: {{hevmane|ku|deste|kom|grûp|taxim}}
#* {{jêder-kitêb|ku|jêgirtin=ئۆە نوکە '''تیما''' مە خوسارەت بوویە|tr=Oe nuke '''tîma''' me xusaret bûye.|sc=ku-Arab|sernav=Bêrîvan û Hîvî (Çîrokêt Zarokan)|nivîskar=Leylan Îsmeîl|weşanxane=Çapxana Xanî|cih=Dihok|sal=2012|r=29}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|تیم}}
==== Jê ====
* {{g|ku|tîmî}}
* {{g|ku|tîmkî}}
==== Etîmolojî ====
{{etîmon|ku|:deyn|en:team|metn=++|dar=1}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Mannschaft}} {{mê}}
* {{Z|ar}}: {{W-|ar|طاقم|guman=erê}}
* {{Z|fa}}: {{W+|fa|تیم}}
* {{Z|en}}: {{W+|en|caboodle}}, {{W+|en|team}}
* {{Z|sv}}: {{W+|sv|lag}}
* {{Z|tr}}: {{W+|tr|takım}}, {{W+|tr|öğür}}, {{W+|tr|tim|guman=erê}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=îmt|mît}}
7k5r454ppiyi75sclfeo7fxcjyd3ku4
6535036
6535035
2026-09-21T00:12:07Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk ({{ku-tewîn-nav/nû}} → {{ku-tewîn-nav}}.)
6535036
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# Çend [[kes]] yan [[tişt]] yên [[li gel]] [[hev]].
#: {{hevmane|ku|deste|kom|grûp|taxim}}
#* {{jêder-kitêb|ku|jêgirtin=ئۆە نوکە '''تیما''' مە خوسارەت بوویە|tr=Oe nuke '''tîma''' me xusaret bûye.|sc=ku-Arab|sernav=Bêrîvan û Hîvî (Çîrokêt Zarokan)|nivîskar=Leylan Îsmeîl|weşanxane=Çapxana Xanî|cih=Dihok|sal=2012|r=29}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|تیم}}
==== Jê ====
* {{g|ku|tîmî}}
* {{g|ku|tîmkî}}
==== Etîmolojî ====
{{etîmon|ku|:deyn|en:team|metn=++|dar=1}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Mannschaft}} {{mê}}
* {{Z|ar}}: {{W-|ar|طاقم|guman=erê}}
* {{Z|fa}}: {{W+|fa|تیم}}
* {{Z|en}}: {{W+|en|caboodle}}, {{W+|en|team}}
* {{Z|sv}}: {{W+|sv|lag}}
* {{Z|tr}}: {{W+|tr|takım}}, {{W+|tr|öğür}}, {{W+|tr|tim|guman=erê}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=îmt|mît}}
24foyxnke0gsqhjyr5w9veowzjid87f
peyman
0
17695
6535016
6438372
2026-09-20T20:23:54Z
Herald2039
15339
/* Bilêvkirin */ add aspiration
6535016
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/pʰɛj.ˈmɑːn/}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[razî|Razîbûn]] bi kirina [[hevkarî|hevkariyê]].
#: {{hevmane|ku|lihevkirin|rêkeftin|pêkhatin|eqd|pakt|mitabeqet|miwafeqet|kontrat|tifaq|itifaq|miwahede|qewl}}
# {{kontrol|rêz=erê|[[qewl|Qewl]], [[hevbendî]], [[miqawele]].}}
==== Ji wêjeya klasîk ====
* {{J:Melayê Cizîrî:Dîwana Melayê Cizîrî|jêgirtin=Durdaneya ez muşterî<br>Kan' 'ehd û '''peymana''' berî<br>Bêfayde çûn şubhê heba}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|پهیمان}}
==== Formên alternatîv ====
* {{g|ku|pêman}}
==== Jê ====
{{kol3|ku|cure=Jê
|peyman çêkirin
|peyman girê dan
|peyman îmze kirin
|peyman pesind kirin
|peyman danîn
|peyman dan
|peyman kirin
|peymandanîn
|peymandan
|peymankirin
|bêpeyman
|bêpeymanî
|bêpeymanîtî
|bêpeymantî
|bipeyman
|bipeymanî
|bipeymanîtî
|bipeymantî
|hevpeyman
|hevpeymanî
|hevpeymanîtî
|hevpeymantî
|peymandar
|peymandarî
|peymandarîtî
|peymandartî
|peymandaner
|peymandanerî
|peymandanerîtî
|peymandanertî
|peymangeh
|peymangir
|peymangirî
|peymangirîtî
|peymanname
}}
==== Etîmolojî ====
Hevreha kurmancî {{q|ku|pîvan}}, hevrehên soranî {{g|ckb|پێوان|tr=[[pêwan]]}} û {{g|ckb|پیوان}}, zazakî {{q|zza|peymane}}, hewramî {{q|hac|pîmay|sc=Latn}}, farisî {{g|fa|پیمودن}}, {{hv|pal||peymûden|sc=Latn}}, {{hv|ae||peytima-|sc=Latn}} û ''vî-med'' ([[bijîşk]]), hemû ji îraniya kevn *peti- + *ma- Ji {{ji|inc-pro|ku|-}} {{recons|inc-pro|peti-}} + *ma-, ji {{ji|ine-pro|ku|*med-}}, jê herwiha {{hv|grc|μέδω}} (mêtho: parastin), latînî ''[[meditor]]'' ([[ramîn]], [[fikirîn]]), ermeniya kevn [[միտ]] ([[hiş]], [[aqil]]), gotî [[𐌼𐌹𐍄𐌰𐌽]] (mitan: [[pîvan]]), {{hv|de|messen}} (pîvan), îngilîzî ''[[mete]]'' ([[pîvan]])
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W-|af|kontrak}}, {{W-|af|ooreenkoms}}
* {{Z|sq}}: {{W+|sq|kontratë|m}}, {{W+|sq|marrëveshje|m}}, {{W+|sq|fejesë}}
* {{Z|de}}: {{W+|de|Vertrag|n}}, {{W+|de|Kontrakt|n}}, {{W+|de|Übereinkommen|nt}}, {{W+|de|Übereinkunft|m}}
* {{Z|az}}: {{W+|az|müqavilə}}, {{W-|az|kontrakt}}
* {{Z|be}}: {{W-|be|кантра́кт|n}}, {{W+|be|дагаво́р|n}}, {{W-|be|дамо́ва|m}}
* {{Z|bn}}: {{W-|bn|চুক্তি}}
* {{Z|bg}}: {{W+|bg|до́говор|n}}, {{W+|bg|контра́кт|n}}
* {{Z|my}}: {{W+|my|ကန်ထရိုက်}}, {{W+|my|စာချုပ်}}
* {{Z|cs}}: {{W+|cs|smlouva|m}}, {{W+|cs|kontrakt|n}}, {{W+|cs|dohoda|m}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|合同}}, {{W-|yue|合約}}, {{W-|yue|契約}}
*: {{Z|cmn}}: {{W+|cmn|合同|tr=hétóng}}, {{W+|cmn|合約}}, {{W+|cmn|契約}}
* {{Z|da}}: {{W+|da|kontrakt|g}}, {{W-|da|traktat|g}}
* {{Z|id}}: {{W+|id|kontrak}}
* {{Z|ar}}: {{W+|ar|عَقْد|n}}, {{W+|ar|مُعَاهَدَة|m}}, {{W-|ar|مُقَاوَلَة|m}}
* {{Z|hy}}: {{W+|hy|պայմանագիր}}
* {{Z|et}}: {{W+|et|leping}}, {{W-|et|kontraht}}
* {{Z|fa}}: {{W+|fa|قَرارداد|tr=qarârdâd}}, {{W+|fa|عَقد|tr='aqd}}, {{W+|fa|پِیمان|tr=peymân}}, {{W+|fa|کُنترات|tr=kontrât}}
* {{Z|fo}}: {{W-|fo|sáttmáli|n}}, {{W-|fo|semja|m}}, {{W-|fo|avtala|m}}
* {{Z|fi}}: {{W+|fi|sopimus}}
* {{Z|fr}}: {{W+|fr|contrat|n}}
* {{Z|gd}}: {{W-|gd|cùmhnant|n}}
* {{Z|ka}}: {{W-|ka|კონტრაქტი}}, {{W-|ka|ხელშეკრულება}}
* {{Z|hi}}: {{W+|hi|संविदा|n}}, {{W+|hi|ठेका|n}}
* {{Z|nl}}: {{W+|nl|contract|nt}}, {{W+|nl|overeenkomst|m}}
* {{Z|he}}: {{W+|he|חוֹזֶה|n|tr=khozé}}
* {{Z|en}}: {{W+|en|contract}}, {{W+|en|agreement}}
* {{Z|ga}}: {{W-|ga|conradh|n}}
* {{Z|it}}: {{W+|it|contratto|n}}
* {{Z|is}}: {{W+|is|samningur|n}}
* {{Z|ja}}: {{W+|ja|契約|tr=けいやく, keiyaku}}
* {{Z|ca}}: {{W+|ca|contracte|n}}
* {{Z|ky}}: {{W-|ky|контракт}}, {{W+|ky|келишим}}
* {{Z|ko}}: {{W+|ko|계약}}
* {{Z|la}}: {{W+|la|foedus|nt}}, {{W+|la|contractus|n}}, {{W-|la|sponsus|n}}
* {{Z|lv}}: {{W-|lv|līgums|n}}, {{W-|lv|kontrakts|n}}
* {{Z|lo}}: {{W+|lo|ສັນຍາ}}, {{W+|lo|ກະຕິກາ}}
* {{Z|lt}}: {{W-|lt|sutartis|n}}, {{W-|lt|kontraktas|n}}
* {{Z|mk}}: {{W-|mk|до́говор|n}}, {{W-|mk|контра́кт|n}}
* {{Z|ms}}: {{W-|ms|kontrak}}
* {{Z|mi}}: {{W-|mi|kānataraka}}
* {{Z|hu}}: {{W+|hu|szerződés}}
* {{Z|mel}}: {{W-|mel|kontrek}}
* {{Z|mn}}:
*: Kirîlî: {{W+|mn|гэрээ}}, {{W+|mn|контракт}}
* {{Z|nv}}: {{W-|nv|ahaʼdeetʼaah}}
* {{Z|nrf}}: {{W-|nrf|contrat|n}}
* {{Z|no}}:
*: {{Z|nb}}: {{W-|nb|kontrakt|n}}, {{W-|nb|traktat|n}}, {{W-|nb|overenskomst|n}}
* {{Z|ug}}: {{W-|ug|شەرتنامە}}
* {{Z|uz}}: {{W+|uz|shartnoma}}, {{W+|uz|bitim}}, {{W+|uz|kontrakt}}
* {{Z|ps}}: {{W+|ps|شرطنامه|m|tr=šartnāma}}, {{W-|ps|عقد|n|tr='aqd}}, {{W-|ps|عهدنامه|m|tr='ahdnāma}}, {{W-|ps|مقاوله|m|tr=moqāwela}}
* {{Z|pl}}: {{W+|pl|umowa|m}}, {{W+|pl|kontrakt|n}}
* {{Z|pt}}: {{W+|pt|contrato|n}}
* {{Z|kk}}: {{W-|kk|контракт}}, {{W-|kk|шарт}}, {{W-|kk|келісім}}
* {{Z|ro}}: {{W+|ro|contract|nt}}
* {{Z|ru}}: {{W+|ru|контра́кт|n}}, {{W+|ru|догово́р|n}}, {{W+|ru|до́говор|n}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|у̏гово̄р|n}}, {{W-|sh|ко̀нтракт|n}}
*: Latînî: {{W+|sh|ȕgovōr|n}}, {{W+|sh|kòntrakt|n}}
* {{Z|sco}}: {{W-|sco|contract}}
* {{Z|sk}}: {{W-|sk|zmluva|m}}, {{W+|sk|dohoda|m}}, {{W-|sk|kontrakt|n}}
* {{Z|sl}}: {{W+|sl|pogọ̑dba|m}}, {{W-|sl|dogȏvor|n}}
* {{Z|es}}: {{W+|es|contrato|n}}
* {{Z|sw}}: {{W+|sw|kandarasi}}
* {{Z|sv}}: {{W+|sv|avtal|nt}}, {{W+|sv|fördrag|g}}, {{W+|sv|kontrakt|nt}}, {{W+|sv|överenskommelse|g}}
* {{Z|tg}}: {{W-|tg|аҳд}}, {{W-|tg|шартнома}}, {{W-|tg|аҳднома}}, {{W-|tg|контракт}}, {{W-|tg|ақд}}
* {{Z|tl}}: {{W-|tl|kontrata}}, {{W-|tl|kasunduan}}
* {{Z|ty}}: {{W-|ty|faʻaauraʻa}}
* {{Z|th}}: {{W+|th|สัญญา}}, {{W+|th|กติกา}}
* {{Z|tt}}: {{W-|tt|шартнамә}}, {{W-|tt|контракт}}
* {{Z|tr}}: {{W+|tr|sözleşme}}, {{W+|tr|kontrat}}
* {{Z|tk}}: {{W-|tk|şertnama}}, {{W-|tk|kontrakt}}
* {{Z|ur}}: {{W-|ur|مُعاہَدَہ|n|tr=mu'āhada}}
* {{Z|uk}}: {{W-|uk|контра́кт|n}}, {{W-|uk|до́говір|n}}, {{W-|uk|догові́р|n}}
* {{Z|vi}}: {{W+|vi|hợp đồng}}, {{W+|vi|khế ước}}
* {{Z|km}}: {{W+|km|សន្យា}}, {{W+|km|កតិកា}}
* {{Z|yi}}: {{W-|yi|קאָנטראַקט|n}}
* {{Z|el}}: {{W+|el|συμβόλαιο|nt}}
{{werger-bin}}
:{{werger çavkanî|en|contract|beş=Ev qismê Wergerê|oldid=75878821}}
orbytgs4yjgj3qwude6uqh9temjgf8v
berhem
0
17881
6534990
6362973
2026-09-20T19:30:30Z
Wikihez
7344
/* Etîmolojî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6534990
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{deng|ku|LL-Q36163 (kmr)-Ebulfîda-berhem.wav|dever=Bidlîs}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[encam|Encama]] [[kar]]ekî; tiştê hatiye çêkirin yan çêbûye.
#: {{hevmane|ku|ber|eser}}
#: {{mk|ku|Îsal zeviyên me '''berhemek''' baş daye.}}
#: {{mk|ku|Ev [[CD]] sêyem '''berhema''' hunermendê ye.}}
#* {{jêder-pirtûk|ku|jêgirtin=Hunermendek dema berhema xwe ava dike ne mumkune ku raman û helwestên xwe jî nexe di nav wê '''berhemê''' de ji ber vê yekê em nikarin '''berhemekê''' ji nivîskarê wê cuda bigirin.|nivîskar=[[w:Reşît Rojdar Yildiz|Reşît Rojdar Yildiz]]|sal=2015|sernav=Di Helbestên Melayê Bateyî de Evîn û Hezkirin|url=https://dergipark.org.tr/en/download/article-file/220241|weşanxane=Bingöl Üniversitesi Yaşayan Diller Enstitüsü Dergisi}}
#* {{J:Firat Cewerî:Payiza Dereng|weşanxane={{w|Avesta}}|sal=2014|r=16|jêgirtin=Gelekan ji wan zû de hevdu nedîtibûn, loma wan li kar û mijûliyên hev dipirsîn, guhertinên ku di wan de bûbûn meraq dikirin, gazin ji jiyana sirgûnê û têkiliyên sar ên ku '''berhemên''' jiyana sirgûnê ne dikirin. Heta ku hebekî xilbexilba wan kêm bû min ji wan daxwaz kir ku ew rabin û ji xwe re xwarinê bînin.}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|بهرههم}}
==== Jê ====
{{kol3|ku|cure=Jê
|bêberhem
|bêberhemî
|bêberhemîtî
|bêberhemtî
|biberhem
|biberhemî
|biberhemîtî
|biberhemtî
|berhemdar
|berhemdarî
|berhemdarîtî
|berhemdartî
|berhemder
|berhemderî
|berhemderîtî
|berhemdertî
|berhemdêr
|berhemdêrî
|berhemdêrîtî
|berhemdêrtî
|berhemî
|berhem dan<q:lêker>
|berhemdan<q:navdêr>
|berhemdayî<q:rengdêr>
}}
==== Etîmolojî ====
Bide ber {{deyn|ckb|ku|بهرههم}}.
;{{biçûk|Çavkanî:
* {{Chyet|r=41}}
}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Produkt|nt}}, {{W+|de|Erzeugnis|nt}}, {{W-|de|des ~ses)''}}, {{W+|de|Arbeit|m}}, {{W+|de|Fabrikat|nt}}, {{W+|de|Erlös|n}}, {{W+|de|Schrift|m}}
* {{Z|ar}}: {{W-|ar|مُنْتَج}}, {{W-|ar|حاصِل}}, {{W-|ar|نِتاج}}, {{W-|ar|ثمار}}, {{W-|ar|إنتاجية}}, {{W-|ar|إنتاج}}, {{W+|ar|صنع}}, {{W-|ar|إخراج}}, {{W-|ar|صناعة}}, {{W+|ar|كتاب}}, {{W-|ar|الجني|guman=erê}}, {{W-|ar|الحصاد|guman=erê}}, {{W-|ar|الطلحة|guman=erê}}, {{W-|ar|النتاج|guman=erê}}, {{W-|ar|ثمر|guman=erê}}, {{W-|ar|حاصل|guman=erê}}
* {{Z|fa}}: {{W+|fa|اثر}}, {{W+|fa|مجموعه}}, {{W+|fa|محصول}}, {{W+|fa|درو|guman=erê}}
* {{Z|fi}}: {{W+|fi|tuote}}
* {{Z|fr}}: {{W+|fr|produit|n}}, {{W+|fr|article|n}}, {{W+|fr|oeuvre|m}}
* {{Z|nl}}: {{W+|nl|productie}}, {{W+|nl|afzet}}
* {{Z|en}}: {{W+|en|product}}, {{W+|en|manufacture}}, {{W+|en|offering}}, {{W+|en|produce}}, {{W+|en|fruit}}, {{W+|en|harvest}}, {{W+|en|article}}, {{W+|en|meal}}, {{W+|en|opus}}, {{W+|en|production}}, {{W+|en|products}}, {{W+|en|vestige}}, {{W+|en|work}}, {{W+|en|yield}}, {{W+|en|output|guman=erê}}, {{W+|en|outturn|guman=erê}}
* {{Z|it}}: {{W+|it|prodotto|n}}
* {{Z|ru}}: {{W+|ru|продукт}}, {{W+|ru|изделие}}, {{W+|ru|произведение}}
* {{Z|es}}: {{W+|es|producto|n}}, {{W+|es|artículo|n}}
* {{Z|sv}}: {{W+|sv|produkt}} ''(-en, {{W+|sv|-er}}, {{W+|sv|-erna}})'', [[alster]] ''({{W+|sv|alstret}}, {{W+|sv|alstren}})''
* {{Z|tr}}: {{W+|tr|ürün}}, {{W+|tr|eser}}, {{W+|tr|hâsılat}}, {{W+|tr|mahsul}}, {{W+|tr|semere}}, {{W+|tr|yapıt}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=beehmr|Behrem|behrem|hember}}
fto9mya973sopfn6m3a3l88019uhmas
6535024
6534990
2026-09-20T21:12:09Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk ({{ku-tewîn-nav/nû}} → {{ku-tewîn-nav}}.)
6535024
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{deng|ku|LL-Q36163 (kmr)-Ebulfîda-berhem.wav|dever=Bidlîs}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[encam|Encama]] [[kar]]ekî; tiştê hatiye çêkirin yan çêbûye.
#: {{hevmane|ku|ber|eser}}
#: {{mk|ku|Îsal zeviyên me '''berhemek''' baş daye.}}
#: {{mk|ku|Ev [[CD]] sêyem '''berhema''' hunermendê ye.}}
#* {{jêder-pirtûk|ku|jêgirtin=Hunermendek dema berhema xwe ava dike ne mumkune ku raman û helwestên xwe jî nexe di nav wê '''berhemê''' de ji ber vê yekê em nikarin '''berhemekê''' ji nivîskarê wê cuda bigirin.|nivîskar=[[w:Reşît Rojdar Yildiz|Reşît Rojdar Yildiz]]|sal=2015|sernav=Di Helbestên Melayê Bateyî de Evîn û Hezkirin|url=https://dergipark.org.tr/en/download/article-file/220241|weşanxane=Bingöl Üniversitesi Yaşayan Diller Enstitüsü Dergisi}}
#* {{J:Firat Cewerî:Payiza Dereng|weşanxane={{w|Avesta}}|sal=2014|r=16|jêgirtin=Gelekan ji wan zû de hevdu nedîtibûn, loma wan li kar û mijûliyên hev dipirsîn, guhertinên ku di wan de bûbûn meraq dikirin, gazin ji jiyana sirgûnê û têkiliyên sar ên ku '''berhemên''' jiyana sirgûnê ne dikirin. Heta ku hebekî xilbexilba wan kêm bû min ji wan daxwaz kir ku ew rabin û ji xwe re xwarinê bînin.}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|بهرههم}}
==== Jê ====
{{kol3|ku|cure=Jê
|bêberhem
|bêberhemî
|bêberhemîtî
|bêberhemtî
|biberhem
|biberhemî
|biberhemîtî
|biberhemtî
|berhemdar
|berhemdarî
|berhemdarîtî
|berhemdartî
|berhemder
|berhemderî
|berhemderîtî
|berhemdertî
|berhemdêr
|berhemdêrî
|berhemdêrîtî
|berhemdêrtî
|berhemî
|berhem dan<q:lêker>
|berhemdan<q:navdêr>
|berhemdayî<q:rengdêr>
}}
==== Etîmolojî ====
Bide ber {{deyn|ckb|ku|بهرههم}}.
;{{biçûk|Çavkanî:
* {{Chyet|r=41}}
}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Produkt|nt}}, {{W+|de|Erzeugnis|nt}}, {{W-|de|des ~ses)''}}, {{W+|de|Arbeit|m}}, {{W+|de|Fabrikat|nt}}, {{W+|de|Erlös|n}}, {{W+|de|Schrift|m}}
* {{Z|ar}}: {{W-|ar|مُنْتَج}}, {{W-|ar|حاصِل}}, {{W-|ar|نِتاج}}, {{W-|ar|ثمار}}, {{W-|ar|إنتاجية}}, {{W-|ar|إنتاج}}, {{W+|ar|صنع}}, {{W-|ar|إخراج}}, {{W-|ar|صناعة}}, {{W+|ar|كتاب}}, {{W-|ar|الجني|guman=erê}}, {{W-|ar|الحصاد|guman=erê}}, {{W-|ar|الطلحة|guman=erê}}, {{W-|ar|النتاج|guman=erê}}, {{W-|ar|ثمر|guman=erê}}, {{W-|ar|حاصل|guman=erê}}
* {{Z|fa}}: {{W+|fa|اثر}}, {{W+|fa|مجموعه}}, {{W+|fa|محصول}}, {{W+|fa|درو|guman=erê}}
* {{Z|fi}}: {{W+|fi|tuote}}
* {{Z|fr}}: {{W+|fr|produit|n}}, {{W+|fr|article|n}}, {{W+|fr|oeuvre|m}}
* {{Z|nl}}: {{W+|nl|productie}}, {{W+|nl|afzet}}
* {{Z|en}}: {{W+|en|product}}, {{W+|en|manufacture}}, {{W+|en|offering}}, {{W+|en|produce}}, {{W+|en|fruit}}, {{W+|en|harvest}}, {{W+|en|article}}, {{W+|en|meal}}, {{W+|en|opus}}, {{W+|en|production}}, {{W+|en|products}}, {{W+|en|vestige}}, {{W+|en|work}}, {{W+|en|yield}}, {{W+|en|output|guman=erê}}, {{W+|en|outturn|guman=erê}}
* {{Z|it}}: {{W+|it|prodotto|n}}
* {{Z|ru}}: {{W+|ru|продукт}}, {{W+|ru|изделие}}, {{W+|ru|произведение}}
* {{Z|es}}: {{W+|es|producto|n}}, {{W+|es|artículo|n}}
* {{Z|sv}}: {{W+|sv|produkt}} ''(-en, {{W+|sv|-er}}, {{W+|sv|-erna}})'', [[alster]] ''({{W+|sv|alstret}}, {{W+|sv|alstren}})''
* {{Z|tr}}: {{W+|tr|ürün}}, {{W+|tr|eser}}, {{W+|tr|hâsılat}}, {{W+|tr|mahsul}}, {{W+|tr|semere}}, {{W+|tr|yapıt}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=beehmr|Behrem|behrem|hember}}
d0xn75tus4abxyju7ftt6kq6mu3zrlz
elzem
0
18828
6534991
6439007
2026-09-20T19:32:41Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534991
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Rengdêr ===
{{rengdêr|ku}}
# [[pêwîst]], [[lazim]], [[gerek]], [[hewce]]
#* {{jêder|ku|jêgirtin="Ji ber ku ji bilî dersên hilbijartinê dê di dibistanên taybet de jî perwerdeya bi zimanê zikmakî bê dayîn ev mijar ji bo me girîng û '''elzem''' e. Min mizgînî ji wan stend. Xebatên 4 hevalên me yên pispor temam bû û dê heta dawiya cotmehê bê weşandin. Ji Tirkî-Kurdî û ji Kurdî-Tirkî. Armanca vê ferhengê ew e ku ji dibistanê heta lîseyê di dersa kurdî ya hilbijartî de û hin xebatên wêjeya Kurdî de bibe alîkar. Texmîn dikim ev ferheng ji 12 hezar peyvan pêk tê."|Bülent Arinçê alîkarê serokwezîrê Tirkiyê li gor www.aa.com.tr/kk 10/2013}}
==== Etîmolojî ====
Ji {{ji|ar|ku|الزم|tr=elzem|w=[[lazim]]tir yan [[lazim]]tirîn}}, têkilî {{g|ar|لازم|tr=lazim|w=[[lazim]]}} û {{g|ar|لزوم|tr=luzûm|w=[[lizûm]], [[lazimî]]}}. Ji eynî rehî: [[iltizam]], [[ilzam]], [[istilzam]], [[lazim]], [[lewazim]], [[malzeme]], [[milazim]], [[miltezim]]
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|unabdingbar}}, {{W|de|unentbehrlich}}
* {{Z|en}}: {{W|en|indispensable|guman=erê}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=eelmz|melez|mezel}}
== {{ziman|tr}} ==
=== Rengdêr ===
{{rengdêr|tr}}
# [[pêwîst]], [[lazim]], [[gerek]], [[hewce]]
==== Anagram ====
* {{anagram|tr|a=eelmz|melez}}
erkzv7ixjqo5xg014f41joof83fc8yf
ecîn
0
22835
6534937
6441401
2026-09-20T17:58:48Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534937
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Çiğ Köfte.jpg|thumb|Ecîn]]
{{navdêr|ku|z=m}}
# [[kufte]], [[kutil]], [[kutilk]], [[kufte]], [[lopik]], [[kuftik]]
#* {{jêder|ku|jêgirtin=Ger Îbo (Îbrahîm Tatlises) bibe wekîl wê dev ji stranan berde û emê jî nikaribin bêyî stranên wî îsot û '''ecînê''' bixwin. Ma îsot û ecîn (çîgkofte) bê ayaginda kundura tên xwarin lo?|Lotikxane.com, 7/2006}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|fa}}: {{W|fa|کوفته}}
* {{Z|fi}}: {{W|fi|lihapulla}}
* {{Z|en}}: {{W|en|meatball}}
* {{Z|tr}}: {{W|tr|köfte}}, {{W|tr|çiğ köfte}}, {{W|tr|çiğköfte}}
{{werger-bin}}
gk6w5qrlaz6y94za32vvt8kddxfjkzm
Wîkîferheng:ceribandin
4
23016
6534965
6531806
2026-09-20T18:44:25Z
Wikihez
7344
Kurmancî/Navdêr: −nimûneya bikaranînê bi amûra « [[Mediawiki:Gadget-wikt.add-examples|Nimûne zêde bike]] » (v2.0), +nimûneya bikaranînê bi amûra « [[Mediawiki:Gadget-wikt.add-examples|Nimûne zêde bike]] » (v2.0) (DefSideBoxes)
6534965
wikitext
text/x-wiki
{{h-bnr|* {{ku-kîte}}
{{/vê rêzê neguhêre}}
== {{ziman|ku}} ==
=== Navdêr ===
{{ku-tewîn-rd|nû}}
[[Wêne:12-10-03-autostadt-wob-by-RalfR-12.jpg|thumb|Ttirimpêleke '''nû'''.]]
[[Wêne:Mobile Phone Evolution 1992 - 2014.jpg|thumb|Çend telefonên destan ji '''nûtirîn''' (li pêş) heta [[kevn]]tirîn (li paş).]]
{{navdêr|ku|c=?}}
# Tiştê ku ne [[ji mêj ve]] çêbûye yan hatiye çêkirin.
#: {{hevmane|ku|teze|cedîd}}
#: {{dijmane|ku|kevn}}
## Tiştê ku [[hê]] nehatiye [[bikaranîn]] yan tenê [[kêm]] hatiye bikaranîn.
##: {{nimûne|ku|kitêba '''ceribandin'''}}
##: {{nimûne|ku|erebeya '''ceribandin'''}}
##: {{mk|ku|cilên '''nû''', pêlavên '''nû'''}}
##* {{Ç:kho:Bailey|head=test|wekjêgirtin=1|jêgirtin=test}}
## Tiştê ku ne ji mêj ve hatiye dîtin yan hayjêbûn.
##: {{mk|ku|Parîs ji min re '''nû''' bû.}}
##* {{jêder|ku|test, 2015|jêgirtin=test}}
##* {{jêder-websîte|ku|websîte=www.dengeamerika.com|sernav=Obama Plana Komarîyan Rexne Dike|url=https://www.dengeamerika.com/amp/obama-plana-komariyan-rexne-dike-126399608/1116339.html|tarîxa-gihiştinê=2026-09-16|tarîx=29.7.2011|jêgirtin=https://www.dengeamerika.com/amp/obama-plana-komariyan-rexne-dike-126399608/1116339.html}}
==== Ji wêjeya klasîk ====
* {{J:Melayê Cizîrî:Dîwana Melayê Cizîrî|jêgirtin=Lê xettê reyhanî li dêm<br />Katib ji '''nû''' i'rab-i da<br />Nazikqeda '''nû'''rînbeşer}}
==== Bi alfabeyên din ====
* {{ku-ar|نوو}}
* {{ku-kr|ну}}
==== Formên alternatîv ====
{{kol3|ku|cure=Herwiha
|new-
|nev-
|nuh
|nûh
|nwî<ee:behdînanî>
|nî<ee:behdînanî>
|nûv
}}
==== Hevmane ====
* [[hecer]]
* [[modern]]
* [[taze]], [[teze]]
==== Dijmane ====
* [[kevn]]
==== Jê ====
{{kol3|ku|cure=Jê
|nû bûn<q:lêker>
|nû kirin<q:lêker>
|nûbûn<q:navdêr>
|nûkirin<q:navdêr>
|nûbûyî<q:rengdêr>
|nûkirî<q:rengdêr>
|nipînû
|nûçe
|nûjen
|nûjenî
|nûjenxwaz
|nûjenxwazî
|nûxwaz
|nûxwazî
|nûyandin
|nûyandî
|nûyîn
|nûyîtî
|venûyandin
|venûyîn
}}
==== Etîmolojî ====
Hevreha soranî {{g|ckb|نوێ|tr=nwê}}, [[kurdiya başûrî]] ''[[nü]]'', hewramî ''[[no]]'', zazakî ''[[newe]]'', farisî {{g|fa|نو|tr=now}}, belûçî {{g|bal|نوک|tr=nok}}, pehlewî [[𐭭𐭥𐭪𐭩]] / [[𐫗𐫇𐫃]] (nwk/nwg /nōg/), peştûyî {{g|ps|نوی|tr=nevey, nivey}}, osetî [[нӕуӕг]] (næwæg) û [[ног]] (nog), bextiyariya kevn [[νωγο]] (nōgo), avestayî [[𐬥𐬀𐬎𐬎𐬀]] (nauua, /newe/)... sanskrîtî [[नव]] (náva), hindî [[नवा]] (navā)... hîtîtî [[𒉋𒀸]] (nēwas), luwiyayî [[𒈾𒀀𒌑𒉿𒋾]] (nāúwati)... bulgarî [[нов]] (nov), çekî ''[[nový]]'', polonî ''[[nowy]]'', rusî [[но́вый]] (nóviy)... latînî ''[[novus]]'', îtalî ''[[novo]], [[nuovo]]'', portugalî ''[[novo]]'', spanî ''[[nuevo]]'', fransî ''[[neuf]], [[nouveau]]''... yûnaniya kevn û nû [[νέος]] (néos)... ermeniya kevn û nû [[նոր]] (nor)... lîtwanî ''[[naũjas]]''... bretonî ''[[nevez]]'', îrlendî ''[[nua]]''... almanî ''[[neu]]'', danmarkî/norwecî/swêdî ''[[ny]]'', holendî ''[[nieuw]]'', îngilîzî ''[[new]]''... hemû ji {{ji|ine-pro|ku|-}} {{recons|ine-pro|néwos}}, {{recons|ine-pro|néwyos}} (nû).
;{{biçûk|Çavkanî:
* {{Chyet}}
* {{Horn}}
* {{MacKenzie-2}}
* {{Pokorny}}
* {{The Tower of Babel}}
* {{Tsabolov}}
* {{Watkins}}
}}
==== Bi zaravayên din ====
* {{Z|ckb}}: {{W-|ckb|نوێ|tr=[[nwê]]}}
* {{Z|sdh}}: {{W-|sdh|sc=Latn|nü}}
* {{Z|hac}}: {{W-|hac|sc=Latn|no}}
* {{Z|zza}}: {{W+|zza|newe}}
=== Rengdêr ===
{{rengdêr|ku}}
# test
csb0tvd8rk8v2u1pa88g3743kmtpgdj
6534972
6534965
2026-09-20T18:53:21Z
Wikihez
7344
/* Navdêr */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6534972
wikitext
text/x-wiki
{{h-bnr|* {{ku-kîte}}
{{/vê rêzê neguhêre}}
== {{ziman|ku}} ==
=== Navdêr ===
{{ku-tewîn-rd|nû}}
[[Wêne:12-10-03-autostadt-wob-by-RalfR-12.jpg|thumb|Ttirimpêleke '''nû'''.]]
[[Wêne:Mobile Phone Evolution 1992 - 2014.jpg|thumb|Çend telefonên destan ji '''nûtirîn''' (li pêş) heta [[kevn]]tirîn (li paş).]]
{{navdêr|ku|c=?}}
# Tiştê ku ne [[ji mêj ve]] çêbûye yan hatiye çêkirin.
#: {{hevmane|ku|teze|cedîd}}
#: {{dijmane|ku|kevn}}
## Tiştê ku [[hê]] nehatiye [[bikaranîn]] yan tenê [[kêm]] hatiye bikaranîn.
##: {{nimûne|ku|kitêba '''ceribandin'''}}
##: {{nimûne|ku|erebeya '''ceribandin'''}}
##: {{mk|ku|cilên '''nû''', pêlavên '''nû'''}}
## Tiştê ku ne ji mêj ve hatiye dîtin yan hayjêbûn.
##: {{mk|ku|Parîs ji min re '''nû''' bû.}}
##* {{jêder|ku|test, 2015|jêgirtin=test}}
##* {{jêder-websîte|ku|websîte=www.dengeamerika.com|sernav=Obama Plana Komarîyan Rexne Dike|url=https://www.dengeamerika.com/amp/obama-plana-komariyan-rexne-dike-126399608/1116339.html|tarîxa-gihiştinê=2026-09-16|tarîx=29.7.2011|jêgirtin=https://www.dengeamerika.com/amp/obama-plana-komariyan-rexne-dike-126399608/1116339.html}}
==== Ji wêjeya klasîk ====
* {{J:Melayê Cizîrî:Dîwana Melayê Cizîrî|jêgirtin=Lê xettê reyhanî li dêm<br />Katib ji '''nû''' i'rab-i da<br />Nazikqeda '''nû'''rînbeşer}}
==== Bi alfabeyên din ====
* {{ku-ar|نوو}}
* {{ku-kr|ну}}
==== Formên alternatîv ====
{{kol3|ku|cure=Herwiha
|new-
|nev-
|nuh
|nûh
|nwî<ee:behdînanî>
|nî<ee:behdînanî>
|nûv
}}
==== Hevmane ====
* [[hecer]]
* [[modern]]
* [[taze]], [[teze]]
==== Dijmane ====
* [[kevn]]
==== Jê ====
{{kol3|ku|cure=Jê
|nû bûn<q:lêker>
|nû kirin<q:lêker>
|nûbûn<q:navdêr>
|nûkirin<q:navdêr>
|nûbûyî<q:rengdêr>
|nûkirî<q:rengdêr>
|nipînû
|nûçe
|nûjen
|nûjenî
|nûjenxwaz
|nûjenxwazî
|nûxwaz
|nûxwazî
|nûyandin
|nûyandî
|nûyîn
|nûyîtî
|venûyandin
|venûyîn
}}
==== Etîmolojî ====
Hevreha soranî {{g|ckb|نوێ|tr=nwê}}, [[kurdiya başûrî]] ''[[nü]]'', hewramî ''[[no]]'', zazakî ''[[newe]]'', farisî {{g|fa|نو|tr=now}}, belûçî {{g|bal|نوک|tr=nok}}, pehlewî [[𐭭𐭥𐭪𐭩]] / [[𐫗𐫇𐫃]] (nwk/nwg /nōg/), peştûyî {{g|ps|نوی|tr=nevey, nivey}}, osetî [[нӕуӕг]] (næwæg) û [[ног]] (nog), bextiyariya kevn [[νωγο]] (nōgo), avestayî [[𐬥𐬀𐬎𐬎𐬀]] (nauua, /newe/)... sanskrîtî [[नव]] (náva), hindî [[नवा]] (navā)... hîtîtî [[𒉋𒀸]] (nēwas), luwiyayî [[𒈾𒀀𒌑𒉿𒋾]] (nāúwati)... bulgarî [[нов]] (nov), çekî ''[[nový]]'', polonî ''[[nowy]]'', rusî [[но́вый]] (nóviy)... latînî ''[[novus]]'', îtalî ''[[novo]], [[nuovo]]'', portugalî ''[[novo]]'', spanî ''[[nuevo]]'', fransî ''[[neuf]], [[nouveau]]''... yûnaniya kevn û nû [[νέος]] (néos)... ermeniya kevn û nû [[նոր]] (nor)... lîtwanî ''[[naũjas]]''... bretonî ''[[nevez]]'', îrlendî ''[[nua]]''... almanî ''[[neu]]'', danmarkî/norwecî/swêdî ''[[ny]]'', holendî ''[[nieuw]]'', îngilîzî ''[[new]]''... hemû ji {{ji|ine-pro|ku|-}} {{recons|ine-pro|néwos}}, {{recons|ine-pro|néwyos}} (nû).
;{{biçûk|Çavkanî:
* {{Chyet}}
* {{Horn}}
* {{MacKenzie-2}}
* {{Pokorny}}
* {{The Tower of Babel}}
* {{Tsabolov}}
* {{Watkins}}
}}
==== Bi zaravayên din ====
* {{Z|ckb}}: {{W-|ckb|نوێ|tr=[[nwê]]}}
* {{Z|sdh}}: {{W-|sdh|sc=Latn|nü}}
* {{Z|hac}}: {{W-|hac|sc=Latn|no}}
* {{Z|zza}}: {{W+|zza|newe}}
=== Rengdêr ===
{{rengdêr|ku}}
# test
haeb02nserdxgjx75fz4ww9nvpmz3a2
6534994
6534972
2026-09-20T19:42:27Z
Wikihez
7344
'{{subst:/Default}}' ket şûna rûpelê.
6534994
wikitext
text/x-wiki
{{/vê rêzê neguhêre}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Lêker ===
[[Wêne:Example.jpg|thumb|Mînak]]
{{lêker|ku|c=h-gh}}
# Maneya 1ê.
#: {{mk|ku|'''Nimûneyeke''' bikaranînê ya vê peyvê.}}
#* {{bênimûne|ku}}
# {{f|ku|mecazî}} Maneya 2an.
#* {{jêder-pirtûk|ku|jêgirtin=Jêgirtinek ji kitêbekê.|nivîskar=Rojen Barnas|sernav=Sîlvan/ Silîvan / FARQÎN|sal=2013|weşandxane=Nûbihar|rûpel=189|isbn= 9786055402884, 6055402882}}
# Maneya 3an.
==== Bikaranîn ====
''Çend not li ser bikaranîna peyvê.''
==== Jê ====
{{kol3|ku|cure=Jê
|ceribandinvan
}}
==== Têkildar ====
* ''Peyvên ku têkiliya [[etîmolojîk]] bi peyvê re hene''.
=== Etîmolojî ===
''Çîroka tarîxî ya eslê kelîmeyê. Nimûneya bandor'': Ji {{deyn|ota|ku|باندره}}, {{q|ota|باكدیره}}, ({{hv|tr|bandıra}}) ji {{ji|vec|ku|bandéra}} ({{hv|it|bandiera}}) ji {{ji|gem|ku}} *''bandwa-'' ({{cotreh|ku|band|bend}}). Bide ber {{hv|fr|bandière}} (wek {{hv|en|banner}}), {{hv|el|παντιέρα}}.
==== Binêre herwiha ====
* ''Peyvên eleqadar lê belê têkiliya etîmolojîk nîne''.
* {{wpedî}}
==== Werger ====
{{werger-ser|1|Maneya 1ê}}
* {{Z|en}}: {{W+|en|word}}
{{werger-bin}}
{{werger-ser|2|Maneya an}}
{{werger-bin}}
{{werger-bnr|Maneya 3an|kelîme}}
=== Navdêr ===
{{ku-tewîn-nav|nav=nimûne|mê}}
{{navdêr|ku|z=m}}
# mane 1
#: mînak 1-1
#: mînak 1-2
#* jêgirtin 1-1
#* jêgirtin 1-2
# mane 2
#: mînak 2-1
#: mînak 2-2
#* jêgirtin 2-1
#* jêgirtin 2-2
# mane 3
==== Werger ====
{{werger-ser|1|Maneya 1ê}}
{{werger-bin}}
{{werger-ser|2|Maneya 2an}}
{{werger-bin}}
{{werger-ser|3|Maneya 3an}}
{{werger-bin}}
=== Çavkanî ===
{{Çavkanî}}
* Çavkaniyên din li vir binivîse.
5afdylow8nel6b8qffhhio63luuzu13
6534998
6534994
2026-09-20T19:44:16Z
Wikihez
7344
/* Lêker */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6534998
wikitext
text/x-wiki
{{/vê rêzê neguhêre}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Lêker ===
[[Wêne:Example.jpg|thumb|Mînak]]
{{lêker|ku|c=h-gh}}
# Maneya 1ê.
#: {{mk|ku|'''Nimûneyeke''' bikaranînê ya vê peyvê.}}
#* {{bênimûne|ku}}
# {{f|ku|mecazî}} Maneya 2an.
#* {{jêder-pirtûk|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}}
# Maneya 3an.
==== Bikaranîn ====
''Çend not li ser bikaranîna peyvê.''
==== Jê ====
{{kol3|ku|cure=Jê
|ceribandinvan
}}
==== Têkildar ====
* ''Peyvên ku têkiliya [[etîmolojîk]] bi peyvê re hene''.
=== Etîmolojî ===
''Çîroka tarîxî ya eslê kelîmeyê. Nimûneya bandor'': Ji {{deyn|ota|ku|باندره}}, {{q|ota|باكدیره}}, ({{hv|tr|bandıra}}) ji {{ji|vec|ku|bandéra}} ({{hv|it|bandiera}}) ji {{ji|gem|ku}} *''bandwa-'' ({{cotreh|ku|band|bend}}). Bide ber {{hv|fr|bandière}} (wek {{hv|en|banner}}), {{hv|el|παντιέρα}}.
==== Binêre herwiha ====
* ''Peyvên eleqadar lê belê têkiliya etîmolojîk nîne''.
* {{wpedî}}
==== Werger ====
{{werger-ser|1|Maneya 1ê}}
* {{Z|en}}: {{W+|en|word}}
{{werger-bin}}
{{werger-ser|2|Maneya an}}
{{werger-bin}}
{{werger-bnr|Maneya 3an|kelîme}}
=== Navdêr ===
{{ku-tewîn-nav|nav=nimûne|mê}}
{{navdêr|ku|z=m}}
# mane 1
#: mînak 1-1
#: mînak 1-2
#* jêgirtin 1-1
#* jêgirtin 1-2
# mane 2
#: mînak 2-1
#: mînak 2-2
#* jêgirtin 2-1
#* jêgirtin 2-2
# mane 3
==== Werger ====
{{werger-ser|1|Maneya 1ê}}
{{werger-bin}}
{{werger-ser|2|Maneya 2an}}
{{werger-bin}}
{{werger-ser|3|Maneya 3an}}
{{werger-bin}}
=== Çavkanî ===
{{Çavkanî}}
* Çavkaniyên din li vir binivîse.
j6xgqztkwo9rvv570i664ruvjma25ul
qepitandin
0
25611
6534938
6443295
2026-09-20T17:59:15Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534938
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Lêker ===
{{ku-tewîn-lk|qepitandin|form=gerguhêz|niha=qepitîn|borî=qepitand}}
{{lêker|ku|c=x-gh}}
# [[qepat]] kirin, [[girtin]], [[daxistin]], [[qifl]] kirin, [[qerase]] kirin
#: {{nm|ku|Derî '''biqepitîne''' daku sarma neyê hindir.}}
#* {{jêder-websîte|ku|jêgirtin=Bila bimire yê ku ji bavê xwe fedî bike, li kul û xemên xwişka xwe guhdarî neke, li diya xwe nepirse, adet û teqalîdên bavûkalên xwe ji bîr bike, zimanê xwe di ser guhan re biavêje û deriyê xwe ji ber dost û hevalan '''biqepitîne'''; bila bimire jê re çêtir e.|nivîskar=Konê Reş|sernav=Bila bimire|websîte=Lalish.de|sal=9/2006}}
# {{kontrol|rêz=erê|[[qifilandin]]}}
==== Tewandin ====
: -qepitîn-
==== Jê ====
{{kol3|ku
|qepitandî
|qepitîner
|qepitînk
}}
==== Têkildar ====
{{kol3|ku
|qepitîn
}}
==== Etîmolojî ====
Ji {{paş|ku|qepat|-andin}}.
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W-|af|afsluit}}, {{W-|af|toemaak}}
* {{Z|de}}: {{W+|de|schließen}}, {{W+|de|verschließen}}, {{W+|de|zuschließen}}, {{W+|de|zumachen}}
* {{Z|zh}}: {{W+|zh|关}}
* {{Z|da}}: {{W-|da|låse}}, {{W+|da|lukke}}
* {{Z|eo}}: {{W-|eo|ŝlosi}}, {{W+|eo|fermi}}
* {{Z|fo}}: {{W-|fo|læsa}}, {{W-|fo|lata aftur}}
* {{Z|fi}}: {{W+|fi|sulkea}}, {{W+|fi|lukita}}
* {{Z|fr}}: {{W+|fr|fermer}}
* {{Z|fy}}: {{W-|fy|skoattelje}}, {{W-|fy|ôfskoattelje}}, {{W-|fy|tichtdwaan}}
* {{Z|gd}}: {{W-|gd|glas}}
* {{Z|nl}}: {{W-|nl|op slot doen}}, {{W+|nl|sluiten}}, {{W+|nl|afsluiten}}, {{W+|nl|dichtdoen}}, {{W+|nl|dichtmaken}}, {{W+|nl|toedoen}}
* {{Z|he}}: {{W+|he|סגר|m}}, {{W+|he|סכר|m}}
* {{Z|ang}}: {{W-|ang|fordyttan}}, {{W-|ang|lucan}}
* {{Z|en}}: to {{W+|en|close}}, to {{W+|en|shut}}, to {{W+|en|lock}}
* {{Z|it}}: {{W+|it|chiudere}}, {{W-|it|serrare a chiave}}
* {{Z|ca}}: {{W-|ca|tancar amb clau}}, {{W+|ca|tancar}}
* {{Z|la}}: {{W-|la|claudere}}
* {{Z|yua}}: {{W-|yua|k’alik}}
* {{Z|pap}}: {{W-|pap|sera}}
* {{Z|pl}}: {{W+|pl|zamykać}}
* {{Z|pt}}: {{W+|pt|fechar}}
* {{Z|ro}}: {{W+|ro|încuia}}, {{W+|ro|închide}}
* {{Z|ru}}: {{W+|ru|закрыть}}, {{W+|ru|закрывать}}
* {{Z|es}}: {{W+|es|cerrar}}, {{W-|es|cerrar con llave}}
* {{Z|sv}}: {{W+|sv|stänga}}
* {{Z|tl}}: {{W-|tl|isará}}
* {{Z|tr}}: {{W+|tr|kapatmak}}
* {{Z|zu}}: {{W-|zu|-khiya}}, {{W-|zu|-vala}}
{{werger-bin}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# {{mane?|ku}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Werger ====
{{werger-ser}}
{{werger-bin}}
2g2mldeg6cbwisw5mnnsu5l89uwyim7
şehmoz
0
26201
6534988
6443693
2026-09-20T19:28:51Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534988
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Rengdêr ===
{{ku-tewîn-rd|şehmoz}}
{{rengdêr|ku}}
# [[heywan|Heywanên]] ne[[tebitî]], ne [[rehet]], ne [[aram]], ne [[miskîn]], ne [[vehewiyayî]] (bi taybetî [[hesp]] û [[dewar]]ên din yên [[zîtik]]an diavêjin).
#: {{hevmane|ku|şûm|bêxulq|şiloq|cirnexweş|neqet|nearam}}
#* {{jêder|ku|Newroz Bawer: ''Dema tundûtûjî bû tiştekî suruştî!'', rojnameya Dema Nû, hj. 147, 4/2006|jêgirtin=Ev nesaxiya evê sîstemê û ehlaqê civatê, xwe li derveyî welat jî nîşan dide. Dema ku topa piyan ya tirkan dest pê dike, mezin û biçûkên wan '''şehmoz''' dibin û dema ku bi ser dikevin, serxweş dibin û wek gurên devbixwîn, êrişê derûdoran dikin.}}
==== Formên alternatîv ====
{{kol3|ku|cure=Herwiha
|şamoz
|şemoz
|şamûz
|şehmûz
|şemûs
|şemûz
}}
==== Jê ====
{{kol3|ku|cure=Jê
|şehmoz bûn<crz:lêker>
|şehmoz kirin<crz:lêker>
|şehmozbûn<crz:navdêr>
|şehmozkirin<crz:navdêr>
|şehmozbûyî<crz:rengdêr>
|şehmozkirî<crz:rengdêr>
|şehmozane
|şehmozî
|şehmozîtî
|şehmoztî
}}
==== Etîmolojî ====
[[hevreh|Hevreha]] pehlewî ''exşoz'', mediya navîn ''*exşoβj'' yan ''*exşûβj'', avestayî ''xšeob-'' ([[arandin]], [[azirandin]], [[ajawe]] kirin) û sanskrîtî [[क्षुभ्यति]] (kṣúbhyati: [[hejandin]]). [[աշխոյժ]] (eşxoyj) ya ermenî ji mediya navîn hatiye deynkirin.
==== Werger ====
{{werger-ser}}
* {{Z|ar}}: {{W-|ar|جانح}}, {{W-|ar|الحصان الجانح}} (hespê şemûs)
* {{Z|en}}: {{W+|en|fretful}}, {{W+|en|thwart}}
{{werger-bin}}
=== Çavkanî ===
* Husein Muhammed: Peyvname II, Kovara Mehname, hj. 60, 1/2005)
6gc2izjvtckiip81dwyx3oossxhu1m0
çîzwîz
0
26989
6534919
6444682
2026-09-20T16:43:56Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534919
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[deng|Deng]]ê [[zirav]] yê ku ji hin [[heywan]]an (bi taybetî ji [[kûçik]]an) tê [[dem]]a ku [[bêçare]] ne.
# [[bêçaretî]], [[gazî]], [[hewar]] (bi taybetî bi dengek [[zirav]] û [[dilsoj]])
#* {{jêder|ku|Lotikxane.com, 10/2006|jêgirtin=Abdullah û Abdulezîz, li Beşaro (Beşar Esed) kirin '''çîzwîz'''}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|çîz-wîz
|çîzûwîz
|çîz û wîz
|çîz-û-wîz
}}
==== Jê ====
{{kol3|ku
|çîzwîzî
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser|çîzwîz kirin}}
* {{Z|fi}}: {{W-|fi|vikistää}}, {{W-|fi|niiskuttaa}}
* {{Z|en}}: to {{W+|en|snivel}}, to {{W+|en|whine}}
{{werger-bin}}
bnlhj1s5em4tf39dmafhlhmpv4ckrqe
6534945
6534919
2026-09-20T18:12:08Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6534945
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[deng|Dengê]] [[zirav]] yê ku ji hin [[heywan]]an (bi taybetî ji [[kûçik]]an) tê [[dem]]a ku [[bêçare]] ne.
# [[bêçaretî]], [[gazî]], [[hewar]] (bi taybetî bi dengek [[zirav]] û [[dilsoj]])
#* {{jêder|ku|Lotikxane.com, 10/2006|jêgirtin=Abdullah û Abdulezîz, li Beşaro (Beşar Esed) kirin '''çîzwîz'''}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|çîz-wîz
|çîzûwîz
|çîz û wîz
|çîz-û-wîz
}}
==== Jê ====
{{kol3|ku
|çîzwîzî
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser|çîzwîz kirin}}
* {{Z|fi}}: {{W-|fi|vikistää}}, {{W-|fi|niiskuttaa}}
* {{Z|en}}: to {{W+|en|snivel}}, to {{W+|en|whine}}
{{werger-bin}}
a2inq72mnpptqm6qhkm1y5ddhn9qcny
mihnet
0
29019
6534927
6446662
2026-09-20T17:45:45Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534927
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[derd]], [[kul]], [[kovan]], [[keser]], [[xem]], [[xefet]]
#* {{J:Feqiyê Teyran:2022|jêgirtin=Dax û kovan bêhisab in <br>'''Mihnet''' û êş û 'ezab in<br> Xem ji qelbê min venabin<br>Ay dilê min, ay dilo!}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|mîhnet
}}
==== Etîmolojî ====
Ji {{ji|ar|ku}}
==== Bide ber ====
{{kol3|ku
|mihne
}}
==== Anagram ====
* {{anagram|ku|a=ehimnt|nihmet}}
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# [[tengasî]], [[kovan]]
13262xojm6grsn5f7t688ihl7q5zvdo
qutî
0
30483
6535031
6448093
2026-09-20T22:39:09Z
Wikihez
7344
Kurmancî/Navdêr 1: beşa «Etîmolojî» hate nûkirin (DefSideBoxes)
6535031
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{deng|ku|LL-Q36163 (kmr)-Key Mîrza-qutî.wav|dever=Bidlîs}}
* {{ku-kîte}}
=== Navdêr 1 ===
[[Wêne:Can(Easy_Open_Can).JPG|thumb|150px|qutiyek]]
{{navdêr|ku|z=m}}
# [[qotîk]], [[sindoq]], [[teneke]], [[qap]]a [[tenekeyî]] ya [[qepat]]
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|قوتی}}
==== Formên alternatîv ====
{{kol3|ku
|qodî
|qodîk
|qotî
|qotîk
|qudî
|qudîk
|qutîk
}}
==== Jê ====
{{kol3|ku
|qutîkî
}}
==== Etîmolojî ====
{{etîmon|ku|:deyn|ota:قوطی<tr:qûtî><etimon:deyn<el:κουτί<etimon:mîrasmayî<grc:κυτίον<qq:forma [[diminutîv]]><etimon:ji<grc:κύτος<t:[[aman]], [[firaq]]>>>>>>>|metn=++|dar=1|niqtena=1}}ku [[hevreh]]a yûnaniya kevn [[σκῦτος]] (skûtos: [[meşk]], [[pîst]], [[post]]) û latînî [[cutis]] ([[pîst]], [[post]]) e. Heman peyv bi rêya tirkî ketiye farisî jî: {{g|fa|قوطی}} (qûtî / ẍûtî)
==== Bi zaravayên din ====
* {{Z|ckb}}: {{W|ckb|قوتوو}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Etui|m}}, {{W+|de|Gefäß|m}}, {{W+|de|Gehäuse|m}}, {{W+|de|Kiste|f}}, {{W+|de|Schachtel|f}}, {{W+|de|Kürze|f|guman=erê}}
* {{Z|ar}}: {{W-|ar|مدقوق|guman=erê}}
* {{Z|fa}}: {{W+|fa|کوفته}}, {{W+|fa|اجمالی|guman=erê}}
* {{Z|en}}: {{W+|en|inoculated}}, {{W+|en|chest}}, {{W+|en|pack|guman=erê}}, {{W+|en|mashed|guman=erê}}, {{W+|en|carton|guman=erê}}
* {{Z|tr}}: {{W+|tr|kutu}}, {{W+|tr|tabaka|guman=erê}}
{{werger-bin}}
{{bend}}
=== Navdêr 2 ===
{{navdêr|ku|z=m}}
# [[kurtî]], [[kinî]], [[qut]]bûn
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Dose|f}}, {{W+|de|Behälter|m}}
{{werger-bin}}
4voq8uw3e67zm8r8b1y3bzrswa6skmj
6535032
6535031
2026-09-20T22:39:53Z
Wikihez
7344
/* Etîmolojî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6535032
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{deng|ku|LL-Q36163 (kmr)-Key Mîrza-qutî.wav|dever=Bidlîs}}
* {{ku-kîte}}
=== Navdêr 1 ===
[[Wêne:Can(Easy_Open_Can).JPG|thumb|150px|qutiyek]]
{{navdêr|ku|z=m}}
# [[qotîk]], [[sindoq]], [[teneke]], [[qap]]a [[tenekeyî]] ya [[qepat]]
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|قوتی}}
==== Formên alternatîv ====
{{kol3|ku
|qodî
|qodîk
|qotî
|qotîk
|qudî
|qudîk
|qutîk
}}
==== Jê ====
{{kol3|ku
|qutîkî
}}
==== Etîmolojî ====
{{etîmon|ku|:deyn|ota:قوطی<tr:qûtî><etimon:deyn<el:κουτί<etimon:mîrasmayî<grc:κυτίον<qq:forma [[diminutîv]]><etimon:ji<grc:κύτος<t:[[aman]], [[firaq]]>>>>>>>|metn=++|dar=1|niqtena=1}} ku [[hevreh]]a yûnaniya kevn [[σκῦτος]] (skûtos: [[meşk]], [[pîst]], [[post]]) û latînî [[cutis]] ([[pîst]], [[post]]) e. Heman peyv bi rêya tirkî ketiye farisî jî: {{g|fa|قوطی}} (qûtî / ẍûtî)
==== Bi zaravayên din ====
* {{Z|ckb}}: {{W|ckb|قوتوو}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Etui|m}}, {{W+|de|Gefäß|m}}, {{W+|de|Gehäuse|m}}, {{W+|de|Kiste|f}}, {{W+|de|Schachtel|f}}, {{W+|de|Kürze|f|guman=erê}}
* {{Z|ar}}: {{W-|ar|مدقوق|guman=erê}}
* {{Z|fa}}: {{W+|fa|کوفته}}, {{W+|fa|اجمالی|guman=erê}}
* {{Z|en}}: {{W+|en|inoculated}}, {{W+|en|chest}}, {{W+|en|pack|guman=erê}}, {{W+|en|mashed|guman=erê}}, {{W+|en|carton|guman=erê}}
* {{Z|tr}}: {{W+|tr|kutu}}, {{W+|tr|tabaka|guman=erê}}
{{werger-bin}}
{{bend}}
=== Navdêr 2 ===
{{navdêr|ku|z=m}}
# [[kurtî]], [[kinî]], [[qut]]bûn
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Dose|f}}, {{W+|de|Behälter|m}}
{{werger-bin}}
9dxtay0v46dvkdr8c1bl9hg4agqnefq
6535033
6535032
2026-09-20T22:44:26Z
Wikihez
7344
Kurmancî/Navdêr 2: beşa «Etîmolojî» hate zêdekirin (DefSideBoxes)
6535033
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{deng|ku|LL-Q36163 (kmr)-Key Mîrza-qutî.wav|dever=Bidlîs}}
* {{ku-kîte}}
=== Navdêr 1 ===
[[Wêne:Can(Easy_Open_Can).JPG|thumb|150px|qutiyek]]
{{navdêr|ku|z=m}}
# [[qotîk]], [[sindoq]], [[teneke]], [[qap]]a [[tenekeyî]] ya [[qepat]]
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|قوتی}}
==== Formên alternatîv ====
{{kol3|ku
|qodî
|qodîk
|qotî
|qotîk
|qudî
|qudîk
|qutîk
}}
==== Jê ====
{{kol3|ku
|qutîkî
}}
==== Etîmolojî ====
{{etîmon|ku|:deyn|ota:قوطی<tr:qûtî><etimon:deyn<el:κουτί<etimon:mîrasmayî<grc:κυτίον<qq:forma [[diminutîv]]><etimon:ji<grc:κύτος<t:[[aman]], [[firaq]]>>>>>>>|metn=++|dar=1|niqtena=1}} ku [[hevreh]]a yûnaniya kevn [[σκῦτος]] (skûtos: [[meşk]], [[pîst]], [[post]]) û latînî [[cutis]] ([[pîst]], [[post]]) e. Heman peyv bi rêya tirkî ketiye farisî jî: {{g|fa|قوطی}} (qûtî / ẍûtî)
==== Bi zaravayên din ====
* {{Z|ckb}}: {{W|ckb|قوتوو}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Etui|m}}, {{W+|de|Gefäß|m}}, {{W+|de|Gehäuse|m}}, {{W+|de|Kiste|f}}, {{W+|de|Schachtel|f}}, {{W+|de|Kürze|f|guman=erê}}
* {{Z|ar}}: {{W-|ar|مدقوق|guman=erê}}
* {{Z|fa}}: {{W+|fa|کوفته}}, {{W+|fa|اجمالی|guman=erê}}
* {{Z|en}}: {{W+|en|inoculated}}, {{W+|en|chest}}, {{W+|en|pack|guman=erê}}, {{W+|en|mashed|guman=erê}}, {{W+|en|carton|guman=erê}}
* {{Z|tr}}: {{W+|tr|kutu}}, {{W+|tr|tabaka|guman=erê}}
{{werger-bin}}
{{bend}}
=== Navdêr 2 ===
{{navdêr|ku|z=m}}
# [[kurtî]], [[kinî]], [[qut]]bûn
==== Etîmolojî ====
{{etîmon|ku|:qertaf|qut|-î|metn=++|dar=1|id=kinî}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Dose|f}}, {{W+|de|Behälter|m}}
{{werger-bin}}
a44mpn0b7ajjci843ppea42x9nplhiu
sınır
0
32143
6534897
6449642
2026-09-20T13:45:04Z
Wikihez
7344
[[Bikarhêner:Wikihez/templateScript2.js|TS]]: Ji [[en:sınır|en.wikt]]
6534897
wikitext
text/x-wiki
{{h-bnr|sinir}}
== {{ziman|tr}} ==
=== Bilêvkirin ===
* {{IPA|tr|/sɯ.nɯɾ/}}
* {{deng|tr|LL-Q256 (tur)-Veravi95-sınır.wav}}
* {{kîte|tr|sı|nır}}
=== Navdêr ===
{{navdêr|tr}}
# [[tixûb]], [[sînor]], [[hidûd]], [[hed]], [[kefşen]]
# [[lîmît]]
#: {{hevmane|tr|limit}}
==== Etîmolojî ====
Ji {{mîr|ota|tr|سنور|tr=sinor, sınor}}, ji {{mîr|trk-oat|tr|سنور|tr=sinor}}, ji {{ji|grc|tr|σύνορον|t=tixûb, hidûd}}, qismen ji {{ji|grk-pro|tr|*wórwos}}, ji {{ji|ine-pro|tr|*werw-}}.
kcexfcchgc0dudz5wpknqhio8pskank
pastel
0
32905
6534942
6450337
2026-09-20T18:02:13Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534942
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|[ˈpaːstɛl]}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Commercial pastels.JPG|thumb|250px|hin pastel]]
{{navdêr|ku|z=m}}
# [[tebeşîr]]in [[rengîn]] yên [[nerm]]
#* {{jêder|ku|jêgirtin=Boyaxên avî, donî an jî '''pastel''' bi kar tînim.|Nevîn Gungor di hevpeyivînekê de li gel Salihê Kevirbirî, Netkurd.com, 2/2007}}
==== Formên alternatîv ====
{{kol3|ku
|pastêl
}}
==== Jê ====
{{kol3|ku
|pastelî
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|fi}}: {{W+|fi|pastelli}}
* {{Z|fr}}: {{W+|fr|pastel}}
* {{Z|en}}: {{W+|en|pastel}}
* {{Z|tr}}: {{W+|tr|pastel}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=aelpst|pestal}}
== {{ziman|en}} ==
=== Bilêvkirin ===
* {{deng|en|LL-Q1860 (eng)-Vealhurl-pastel.wav|Deng|dever=Başûrê Îngilistanê, QY}}
* {{deng|en|LL-Q1860 (eng)-Wodencafe-pastel.wav|Deng|dever=Teksas, DYA}}
=== Navdêr ===
{{navdêr|en}}
# heşa şîn, [[rengvekirî]], [[tenik]], wêneyek torî raveyi sivik, toza çêki rina qelemên rengî, hevîrê hişkê heşî û ava şirêzî<ref>Ferhenga Kurdî-Îngîlîzî ya Salah Sadallah</ref>
==== Anagram ====
* {{anagram|en|a=aelpst|petals|plates|staple}}
== {{ziman|tr}} ==
=== Rengdêr ===
{{rengdêr|tr}}
# [[pastel]]
# rengê çilmisî
=== Çavkanî ===
{{çavkanî}}
g39hdpndk72szxp09p71tbenst1vu1d
ehemmiyet
0
35382
6534924
6453408
2026-09-20T17:42:52Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534924
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[giringî]], [[pêdivîtî]], [[mihimî]], [[giringbûn]], [[mihimbûn]]
#* {{jêder-websîte|ku|jêgirtin=Şagirtekê wî yê bi navê Muhsin hebû li Stenbolê. Muhsin dibêje seyda Bedîuzzeman gelik ji gera biharê hez dikir. Bi hatina biharê pir kêfxweş û şadman dibû. Dixwest seyr û temaşaya mexlûqatên kaînatê bikit û tefekkûrê li ser bigerînit. Roja newrozê em bi xwe re birin geştê. Ji me re behsa '''ehemmîyet'''a newrozê kir. Got newroz cejna mexlûqatan e. Li wê derê hinek kûçik hebûn. Hinek nan da wa kûçikan. Got îro rojeka weqas pîroz e hita ko behra van kûçikan jî di vê newrozê de heye. Ew jî tevlî newroza vê kaînatê bibin. Welê dîyar bû ko roja newrozê gelik kêfxweş dibit.|nivîskar=Ronî Evdirehman|sernav=Bedîuzzeman û cejna newrozê|websîte=Nefel.com|sal=3/2007}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|ehemiyet
}}
==== Etîmolojî ====
Ji {{ji|ar|ku}}
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# [[giringî]], [[mihîmî]]
jlqwwgyhj5nlx44klldvrvmk265hyno
seytere
0
35773
6534941
6453946
2026-09-20T18:01:39Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534941
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[kontrol]], [[binemrî]], [[zeft]], [[berbendî]]
# [[niqte]]yên ku [[esker]] yan [[polîs]] mirovan lê li ser [[rê]]yan [[rawestandin|radiwestînin]] daku [[kontrol]] bikin
#* {{jêder|ku|jêgirtin=Li wan cihên ko medyaya tirkan behsê dike tevgereka eskerî ya ”nenormal” nîne û xelkê ti konvoyên eskerî jî nedîtine. Otomobîlên eskerî yên ko diyar dibin jî ew in yên ko diçin bo karakol û merkezên eskerî yên berê. Li '''seytereyên''' eskerî yên wekî Qesirgê, Şirnex, Gundikê Melî, Sêgirkê û cihên dî kontrol ji berê zêdetir û dijwartir e û xelkê vê deverê wê jî bi hatina newrozê ve girêdide û difikire ko esker dixwaze hebûna xwe nîşan bide û xwe bîne bîra xelkê.|Nefel.com, 3/2007}}
==== Tewandin ====
{{ku-tewîn-nav|mê|guherto=hemû}}
==== Etîmolojî ====
Ji {{ji|ar}}.
==== Werger ====
{{werger-ser}}
* {{Z|en}}: {{W+|en|checkpoint}}
* {{Z|tr}}: {{W+|tr|kontrol}}
{{werger-bin}}
rq2kknf1oavddin320bgc23kgpoqtk0
tewcih
0
40085
6534956
6459952
2026-09-20T18:30:53Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534956
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[fetilandin]], [[ber]]ê kesekî yan tiştekî dan [[der]]ekê, <br>bi [[alî|aliyekî]] ve [[zivirandin]]
# ([[meqam]], [[mewkî]]) [[terfî]]kirin, [[araste]]kirin
# [[serkêşî]]kirin, [[rêberî]]kirin, [[pêşengî]]kirin
#* {{jêder|ku|jêgirtin=Mamosteyên ayînê îslamê rolekê serekî di şoreşa mezina Eylûlê de hebû. Mamosteyên ayînî mil bi milê pêşmergeyî şer dikir, divêt mamosteyên ayînî nuke jî wî rolî bibîne, rolekê '''tewcîhî''' û îrşadî û piştevanî jî bo hikûmetê.|Mesûd Barzanî li gor Nefel.com, 6/2007}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|tewcîh
}}
==== Jê ====
{{kol3|ku
|tewcihî
|tewcîhker
}}
==== Etîmolojî ====
Ji {{ji|ar|ku}}
==== Werger ====
{{werger-ser|tewcih kirin}}
* {{Z|de}}: {{W+|de|richten}}, {{W+|de|verleihen}}
* {{Z|fi}}: {{W+|fi|viitoittaa}}, {{W+|fi|osoittaa}}, {{W+|fi|johtaa}}
* {{Z|en}}: to {{W+|en|direct}}, to [[point]] towards sth
* {{Z|tr}}: [[tevcih]] etmek, {{W+|tr|yöneltmek}}
{{werger-bin}}
43x8igwd7ujmx5sukrk53jjvk10x2eb
hambûrger
0
40273
6534957
6460164
2026-09-20T18:31:23Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534957
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Hamburger sandwich.jpg|thumb|250px|hambûrgerek]]
{{navdêr|ku|z=m}}
# [[sandwîç]]ên [[goşt]]ê [[germ]] û [[carine]] jî [[zelete]] di [[nav]] re
#* {{jêder|ku|jêgirtin=Xanimê (Lindsay Lohan) nikare bê seks û '''hambûrger''' îdare bike|Lotikxane.com, 6/2007}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|hamburger
|burger
|bûrger
}}
==== Jê ====
{{kol3|ku
|hambûrgerî
|hambûrgergeh
|hambûrgervan
|hambûrgerxane
}}
==== Etîmolojî ====
Bi rêya îngilîzî [[hamburger]] ji {{hv|de|Hamburger}} ([[hambûrgî]], ji [[Hambûrg]]ê, ya / yê [[Hambûrg]]ê)
==== Werger ====
{{werger-ser}}
* {{Z|zh}}: {{W+|zh|漢堡包}}
* {{Z|fi}}: {{W+|fi|hampurilainen}}
* {{Z|fr}}: {{W|fr|hamburger|m}}
* {{Z|nl}}: {{W|nl|hamburger}}
* {{Z|en}}: {{W+|en|hamburger}}, {{W+|en|burger}}
* {{Z|it}}: {{W|it|hamburger|m}}
* {{Z|ja}}: {{W-|ja|ハンバーガー|tr=hanbāgā}}
* {{Z|ko}}: {{W-|ko|햄버거|tr=haembeogeo}}
* {{Z|mt}}: {{W|mt|ħemberger|f}}
* {{Z|pl}}: {{W|pl|hamburger|m}}
* {{Z|ru}}: {{W|ru|гамбургер|m}}
* {{Z|es}}: {{W|es|hamburguesa|f}}
* {{Z|sv}}: {{W|sv|hamburgare|c}}
* {{Z|tr}}: {{W+|tr|hamburger}}
* {{Z|cy}}: {{W+|cy|byrger}}
* {{Z|el}}: {{W|el|χάμπουργκερ|n}}
{{werger-bin}}
qlj7nbfhu90n1wg4jb6wkhg6bc7bxts
6534958
6534957
2026-09-20T18:38:22Z
Wikihez
7344
Kurmancî/Navdêr: mane hate guhartin, −nimûneya bikaranînê bi amûra « [[Mediawiki:Gadget-wikt.add-examples|Nimûne zêde bike]] » (v2.0) (DefSideBoxes)
6534958
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Hamburger sandwich.jpg|thumb|250px|hambûrgerek]]
{{navdêr|ku|z=m}}
# [[sandwîç|Sandwîç]]ên [[goşt]]ê [[germ]] û [[carine]] jî [[zelete]] di [[nav]] re.
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|hamburger
|burger
|bûrger
}}
==== Jê ====
{{kol3|ku
|hambûrgerî
|hambûrgergeh
|hambûrgervan
|hambûrgerxane
}}
==== Etîmolojî ====
Bi rêya îngilîzî [[hamburger]] ji {{hv|de|Hamburger}} ([[hambûrgî]], ji [[Hambûrg]]ê, ya / yê [[Hambûrg]]ê)
==== Werger ====
{{werger-ser}}
* {{Z|zh}}: {{W+|zh|漢堡包}}
* {{Z|fi}}: {{W+|fi|hampurilainen}}
* {{Z|fr}}: {{W|fr|hamburger|m}}
* {{Z|nl}}: {{W|nl|hamburger}}
* {{Z|en}}: {{W+|en|hamburger}}, {{W+|en|burger}}
* {{Z|it}}: {{W|it|hamburger|m}}
* {{Z|ja}}: {{W-|ja|ハンバーガー|tr=hanbāgā}}
* {{Z|ko}}: {{W-|ko|햄버거|tr=haembeogeo}}
* {{Z|mt}}: {{W|mt|ħemberger|f}}
* {{Z|pl}}: {{W|pl|hamburger|m}}
* {{Z|ru}}: {{W|ru|гамбургер|m}}
* {{Z|es}}: {{W|es|hamburguesa|f}}
* {{Z|sv}}: {{W|sv|hamburgare|c}}
* {{Z|tr}}: {{W+|tr|hamburger}}
* {{Z|cy}}: {{W+|cy|byrger}}
* {{Z|el}}: {{W|el|χάμπουργκερ|n}}
{{werger-bin}}
iwf1icwpy67za6pbhnbynhs72j4t08l
6534959
6534958
2026-09-20T18:40:06Z
Wikihez
7344
Kurmancî/Navdêr: +nimûneya bikaranînê bi amûra « [[Mediawiki:Gadget-wikt.add-examples|Nimûne zêde bike]] » (v2.0) (DefSideBoxes)
6534959
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Hamburger sandwich.jpg|thumb|250px|hambûrgerek]]
{{navdêr|ku|z=m}}
# [[sandwîç|Sandwîç]]ên [[goşt]]ê [[germ]] û [[carine]] jî [[zelete]] di [[nav]] re.
#* {{jêder-kovar|ku|nivîskar=Mûrad Ciwan|sernav=Ma însan mexlûqatekî siyasî ye?|kovar=Armanc|hejmar=5|sal=1995|rûpel=3|jêgirtin=Dolme, savar, fasûliyên hişk, çîgkofte an hamburger pê xweş e. Belkî ji dewî, avê, araqê an wîskîyê hez dike û h.w.d.}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|hamburger
|burger
|bûrger
}}
==== Jê ====
{{kol3|ku
|hambûrgerî
|hambûrgergeh
|hambûrgervan
|hambûrgerxane
}}
==== Etîmolojî ====
Bi rêya îngilîzî [[hamburger]] ji {{hv|de|Hamburger}} ([[hambûrgî]], ji [[Hambûrg]]ê, ya / yê [[Hambûrg]]ê)
==== Werger ====
{{werger-ser}}
* {{Z|zh}}: {{W+|zh|漢堡包}}
* {{Z|fi}}: {{W+|fi|hampurilainen}}
* {{Z|fr}}: {{W|fr|hamburger|m}}
* {{Z|nl}}: {{W|nl|hamburger}}
* {{Z|en}}: {{W+|en|hamburger}}, {{W+|en|burger}}
* {{Z|it}}: {{W|it|hamburger|m}}
* {{Z|ja}}: {{W-|ja|ハンバーガー|tr=hanbāgā}}
* {{Z|ko}}: {{W-|ko|햄버거|tr=haembeogeo}}
* {{Z|mt}}: {{W|mt|ħemberger|f}}
* {{Z|pl}}: {{W|pl|hamburger|m}}
* {{Z|ru}}: {{W|ru|гамбургер|m}}
* {{Z|es}}: {{W|es|hamburguesa|f}}
* {{Z|sv}}: {{W|sv|hamburgare|c}}
* {{Z|tr}}: {{W+|tr|hamburger}}
* {{Z|cy}}: {{W+|cy|byrger}}
* {{Z|el}}: {{W|el|χάμπουργκερ|n}}
{{werger-bin}}
mpma6seij4vofwt08h4m2za5cfrwogz
6534960
6534959
2026-09-20T18:40:30Z
Wikihez
7344
Kurmancî/Navdêr: ±nimûneya bikaranînê bi amûra « [[Mediawiki:Gadget-wikt.add-examples|Nimûne zêde bike]] » (v2.0) (DefSideBoxes)
6534960
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Hamburger sandwich.jpg|thumb|250px|hambûrgerek]]
{{navdêr|ku|z=m}}
# [[sandwîç|Sandwîç]]ên [[goşt]]ê [[germ]] û [[carine]] jî [[zelete]] di [[nav]] re.
#* {{jêder-kovar|ku|nivîskar=Mûrad Ciwan|sernav=Ma însan mexlûqatekî siyasî ye?|kovar=Armanc|hejmar=5|sal=1995|rûpel=3|jêgirtin=Dolme, savar, fasûliyên hişk, çîgkofte an '''hamburger''' pê xweş e. Belkî ji dewî, avê, araqê an wîskîyê hez dike û h.w.d.}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|hamburger
|burger
|bûrger
}}
==== Jê ====
{{kol3|ku
|hambûrgerî
|hambûrgergeh
|hambûrgervan
|hambûrgerxane
}}
==== Etîmolojî ====
Bi rêya îngilîzî [[hamburger]] ji {{hv|de|Hamburger}} ([[hambûrgî]], ji [[Hambûrg]]ê, ya / yê [[Hambûrg]]ê)
==== Werger ====
{{werger-ser}}
* {{Z|zh}}: {{W+|zh|漢堡包}}
* {{Z|fi}}: {{W+|fi|hampurilainen}}
* {{Z|fr}}: {{W|fr|hamburger|m}}
* {{Z|nl}}: {{W|nl|hamburger}}
* {{Z|en}}: {{W+|en|hamburger}}, {{W+|en|burger}}
* {{Z|it}}: {{W|it|hamburger|m}}
* {{Z|ja}}: {{W-|ja|ハンバーガー|tr=hanbāgā}}
* {{Z|ko}}: {{W-|ko|햄버거|tr=haembeogeo}}
* {{Z|mt}}: {{W|mt|ħemberger|f}}
* {{Z|pl}}: {{W|pl|hamburger|m}}
* {{Z|ru}}: {{W|ru|гамбургер|m}}
* {{Z|es}}: {{W|es|hamburguesa|f}}
* {{Z|sv}}: {{W|sv|hamburgare|c}}
* {{Z|tr}}: {{W+|tr|hamburger}}
* {{Z|cy}}: {{W+|cy|byrger}}
* {{Z|el}}: {{W|el|χάμπουργκερ|n}}
{{werger-bin}}
rksx65pyykffairh65n368ro8xs04ke
6535009
6534960
2026-09-20T20:12:18Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6535009
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Hamburger sandwich.jpg|thumb|250px|hambûrgerek]]
{{navdêr|ku|z=m}}
# [[sandwîç|Sandwîçên]] [[goşt]]ê [[germ]] û [[carine]] jî [[zelete]] di [[nav]] re.
#* {{jêder-kovar|ku|nivîskar=Mûrad Ciwan|sernav=Ma însan mexlûqatekî siyasî ye?|kovar=Armanc|hejmar=5|sal=1995|rûpel=3|jêgirtin=Dolme, savar, fasûliyên hişk, çîgkofte an '''hamburger''' pê xweş e. Belkî ji dewî, avê, araqê an wîskîyê hez dike û h.w.d.}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|hamburger
|burger
|bûrger
}}
==== Jê ====
{{kol3|ku
|hambûrgerî
|hambûrgergeh
|hambûrgervan
|hambûrgerxane
}}
==== Etîmolojî ====
Bi rêya îngilîzî [[hamburger]] ji {{hv|de|Hamburger}} ([[hambûrgî]], ji [[Hambûrg]]ê, ya / yê [[Hambûrg]]ê)
==== Werger ====
{{werger-ser}}
* {{Z|zh}}: {{W+|zh|漢堡包}}
* {{Z|fi}}: {{W+|fi|hampurilainen}}
* {{Z|fr}}: {{W|fr|hamburger|m}}
* {{Z|nl}}: {{W|nl|hamburger}}
* {{Z|en}}: {{W+|en|hamburger}}, {{W+|en|burger}}
* {{Z|it}}: {{W|it|hamburger|m}}
* {{Z|ja}}: {{W-|ja|ハンバーガー|tr=hanbāgā}}
* {{Z|ko}}: {{W-|ko|햄버거|tr=haembeogeo}}
* {{Z|mt}}: {{W|mt|ħemberger|f}}
* {{Z|pl}}: {{W|pl|hamburger|m}}
* {{Z|ru}}: {{W|ru|гамбургер|m}}
* {{Z|es}}: {{W|es|hamburguesa|f}}
* {{Z|sv}}: {{W|sv|hamburgare|c}}
* {{Z|tr}}: {{W+|tr|hamburger}}
* {{Z|cy}}: {{W+|cy|byrger}}
* {{Z|el}}: {{W|el|χάμπουργκερ|n}}
{{werger-bin}}
47b509hoi3u8h00xti1f03mhbz7bon5
fitûl
0
40430
6534968
6460308
2026-09-20T18:48:16Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534968
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[lêb]], [[hîle]], [[xap]], [[dekûdolab]], [[qîle]], [[fêl]], [[fen]], [[xapandin]], [[xirrandin]], [[fetl]]
#* {{jêder|ku|Demanu.com.tr, 6/2007|jêgirtin=Biratî xweş e raste-rast, ne bi fetl û '''fitûl'''.}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Jê ====
{{kol3|ku
|fitûlî
}}
==== Etîmolojî ====
Ji {{deyn|ar|ku}}, [[pirrjimarî|pirrjimariya]] [[fetl]]
==== Bide ber ====
{{kol3|ku
|fitîl
}}
==== Werger ====
{{werger-ser}}
* {{Z|fi}}: {{W+|fi|huijaus}}
* {{Z|en}}: {{W+|en|deceit}}, {{W+|en|fraud}}, {{W+|en|cheat}}, {{W+|en|trick}}, {{W+|en|deception}}, {{W+|en|decoy}}, {{W+|en|cheating}}, {{W+|en|canard}}
* {{Z|tr}}: {{W+|tr|hile}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=filtû|litûf|tûfil}}
9vhpfdxuwwcvupqt3pe7lvsiclibhqh
dîskûr
0
40764
6534970
6460917
2026-09-20T18:51:21Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534970
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[axiftin|Axiftin]]a li ser [[mijar]]ekê; [[peyivîn|Peyivîn]]a li ser [[babet]]ekî.
#: {{hevmane|ku|gotûbêj|diyalog|diyalekt|minaqeşe|nîqaş|suhbet|danûstandin}}
#* {{jêder-websîte|ku|jêgirtin=Warê romanekê girîng e, ji ber ku warek bi qasî ku bandorê li ziman û şêwaza nivîskêr bike xurt e. Her war li gorî atmosfera xwe xwedî termînolojî û ferhengekê ye; her war li gorî naverok û teşeya xwe xwedî ziman û şêwazekê ye; carinan '''dîskûr''' û diyalogên warekî, ji bo warekî din dikare xav, sixte, bêxwîn û kêmbawer be. Ew jî wiha ye, heger diyalogên romanekê li warê parlamentoyekê derbas bibe, ziman û şêwaza nivîskêr jî li gorî wî warî diguhere; sar dibe, fermî dibe û polîtîk dibe. Heger îcar warê berhemê ne parlamento lê dikan an jî tamîrxaneyek be ziman rojane, germ, bi argo û bi sixêf dibe. Lê heger warê romanê ne dikan lê bibe mizgeftek îcar ziman û şêwaza nivîskêr dîsa tê guhertin argo, sixêf û peyvên beradayî ji holê radibin û termînolojiya teolojîk dikeve dewrê û dua, selewat û gotinên mîstîk zorê didin nivîskarê xwe.|nivîskar=Rênas Jiyan|sernav=Warê wêjeyê|websîte=Azadiyawelat.com|sal=6/2007}}
# {{e|ku|zimannasî|û|edebiyat}} [[gotar]], [[rabêj]]
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|dîskur
|dîskurs
|dîskûrs
}}
==== Jê ====
{{kol3|ku
|dîskûrî
}}
==== Etîmolojî ====
Ji {{ji|fr|ku|discours}}.
==== Werger ====
{{werger-ser}}
* {{Z|fi}}: {{W+|fi|diskurssi}}
* {{Z|fr}}: {{W+|fr|discours}}
* {{Z|en}}: {{W+|en|discourse}}
* {{Z|it}}: {{W|it|discorso|m}}
* {{Z|ja}}: {{W+|ja|会話}}
* {{Z|pt}}: {{W|pt|discussão|f}}
* {{Z|ro}}: {{W|ro|discurs|n}}, {{W|ro|conversaţie|f}}
* {{Z|tr}}: {{W+|tr|diyalekt}}
* {{Z|el}}: [[συνομιλία]] inomilia)
{{werger-bin}}
ppzl6w1c21b3wqxiy2pcuqcjheip04a
6535008
6534970
2026-09-20T20:12:15Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6535008
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[axiftin|Axiftina]] li ser [[mijar]]ekê; [[peyivîn|Peyivîna]] li ser [[babet]]ekî.
#: {{hevmane|ku|gotûbêj|diyalog|diyalekt|minaqeşe|nîqaş|suhbet|danûstandin}}
#* {{jêder-websîte|ku|jêgirtin=Warê romanekê girîng e, ji ber ku warek bi qasî ku bandorê li ziman û şêwaza nivîskêr bike xurt e. Her war li gorî atmosfera xwe xwedî termînolojî û ferhengekê ye; her war li gorî naverok û teşeya xwe xwedî ziman û şêwazekê ye; carinan '''dîskûr''' û diyalogên warekî, ji bo warekî din dikare xav, sixte, bêxwîn û kêmbawer be. Ew jî wiha ye, heger diyalogên romanekê li warê parlamentoyekê derbas bibe, ziman û şêwaza nivîskêr jî li gorî wî warî diguhere; sar dibe, fermî dibe û polîtîk dibe. Heger îcar warê berhemê ne parlamento lê dikan an jî tamîrxaneyek be ziman rojane, germ, bi argo û bi sixêf dibe. Lê heger warê romanê ne dikan lê bibe mizgeftek îcar ziman û şêwaza nivîskêr dîsa tê guhertin argo, sixêf û peyvên beradayî ji holê radibin û termînolojiya teolojîk dikeve dewrê û dua, selewat û gotinên mîstîk zorê didin nivîskarê xwe.|nivîskar=Rênas Jiyan|sernav=Warê wêjeyê|websîte=Azadiyawelat.com|sal=6/2007}}
# {{e|ku|zimannasî|û|edebiyat}} [[gotar]], [[rabêj]]
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|dîskur
|dîskurs
|dîskûrs
}}
==== Jê ====
{{kol3|ku
|dîskûrî
}}
==== Etîmolojî ====
Ji {{ji|fr|ku|discours}}.
==== Werger ====
{{werger-ser}}
* {{Z|fi}}: {{W+|fi|diskurssi}}
* {{Z|fr}}: {{W+|fr|discours}}
* {{Z|en}}: {{W+|en|discourse}}
* {{Z|it}}: {{W|it|discorso|m}}
* {{Z|ja}}: {{W+|ja|会話}}
* {{Z|pt}}: {{W|pt|discussão|f}}
* {{Z|ro}}: {{W|ro|discurs|n}}, {{W|ro|conversaţie|f}}
* {{Z|tr}}: {{W+|tr|diyalekt}}
* {{Z|el}}: [[συνομιλία]] inomilia)
{{werger-bin}}
198gr4r9kdk93vs2y2sy2rwghawuo8v
pertew
0
40957
6534943
6461259
2026-09-20T18:06:16Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534943
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[ronahî]], [[rewnaq]], [[şewq]], [[rewşen]], [[geşî]]
#* {{jêder|ku|Cigerxwîn: ''Bilbil û Gul''|jêgirtin=Ger ji dêmên sorgulî <br>'''Pertew''' nedî nêv can û dil<br>Nakevî nêv bend û dava <br>Zulfê mest û fulfil e.}}
# {{kontrol|rêz=erê|[[ronî]]}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Jê ====
{{kol3|ku
|pertewdar
|pertewî
}}
==== Etîmolojî ====
Hevreha yan ji {{ji|fa|ku|پرتو|tr=pertev}} ji {{ji|ira}} [[per-]] ([[ber]], li ber) + [[tav]]
==== Werger ====
{{werger-ser}}
* {{Z|tr}}: {{W+|tr|ışık}}
{{werger-bin}}
0ybq8u4klchtxlhkv48ke8ciu391eru
wînç
0
41663
6534930
6462145
2026-09-20T17:54:52Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534930
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Seilwinde (fcm).jpg|thumb|250px|wînçek]]
{{navdêr|ku|z=m}}
# [[amûr|Amûr]]a ku [[werîs]] yan [[bizîng]] lê tê [[badan]] û tiştên [[giran]] pê tên [[bilind]]kirin yan [[kêşan]].
#: {{hevmane|ku|niwêl|siling|ceresqe}}
#* {{jêder|ku|Nefel.com, 7/2007|jêgirtin=Pênc hostayên berfeşîrê (dondurma) yên kurd ko ji Mereşê ne li qezaya Seydişehirê ya Konyayê berfeşîreka mezin ko ji 400 kg pêk dihat çêkir û bi wê berfeşîrê mînîbuseka 2, 5 ton bi '''vînçê''' rakirin hewa û di bin mînîbusê de jî govenda kurdî kirin.}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|vinç
|vînç
|winç
}}
==== Etîmolojî ====
Ji {{ji|en|ku|winch}}.
==== Werger ====
{{werger-ser}}
* {{Z|sq}}: {{W+|sq|argano}}
* {{Z|de}}: {{W+|de|Seilwinde}}
* {{Z|cs}}: {{W-|cs|navíjecí buben}}
* {{Z|zh}}: {{W+|zh|絞}}, {{W+|zh|绞}}
* {{Z|eo}}: {{W-|eo|vinĉo}}
* {{Z|fi}}: {{W+|fi|vinssi}}, {{W+|fi|vintturi}}
* {{Z|fr}}: {{W+|fr|treuil}}
* {{Z|nl}}: [[lier]] (hijs- of trekwerktuig)
* {{Z|he}}: {{W+|he|כננת}}
* {{Z|en}}: {{W+|en|winch}}, {{W-|en|differential windlass|guman=erê}}
* {{Z|no}}: {{W-|no|vinsj}}
* {{Z|pt}}: {{W+|pt|catraca}}
* {{Z|ru}}: {{W+|ru|лебёдка}}
* {{Z|sv}}: {{W+|sv|vinsch}}
* {{Z|tr}}: {{W+|tr|vinç}}
{{werger-bin}}
7r2a2t1j0wmbb2uunjn7ezkl4c925tp
6534980
6534930
2026-09-20T19:12:26Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6534980
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Seilwinde (fcm).jpg|thumb|250px|wînçek]]
{{navdêr|ku|z=m}}
# [[amûr|Amûra]] ku [[werîs]] yan [[bizîng]] lê tê [[badan]] û tiştên [[giran]] pê tên [[bilind]]kirin yan [[kêşan]].
#: {{hevmane|ku|niwêl|siling|ceresqe}}
#* {{jêder|ku|Nefel.com, 7/2007|jêgirtin=Pênc hostayên berfeşîrê (dondurma) yên kurd ko ji Mereşê ne li qezaya Seydişehirê ya Konyayê berfeşîreka mezin ko ji 400 kg pêk dihat çêkir û bi wê berfeşîrê mînîbuseka 2, 5 ton bi '''vînçê''' rakirin hewa û di bin mînîbusê de jî govenda kurdî kirin.}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|vinç
|vînç
|winç
}}
==== Etîmolojî ====
Ji {{ji|en|ku|winch}}.
==== Werger ====
{{werger-ser}}
* {{Z|sq}}: {{W+|sq|argano}}
* {{Z|de}}: {{W+|de|Seilwinde}}
* {{Z|cs}}: {{W-|cs|navíjecí buben}}
* {{Z|zh}}: {{W+|zh|絞}}, {{W+|zh|绞}}
* {{Z|eo}}: {{W-|eo|vinĉo}}
* {{Z|fi}}: {{W+|fi|vinssi}}, {{W+|fi|vintturi}}
* {{Z|fr}}: {{W+|fr|treuil}}
* {{Z|nl}}: [[lier]] (hijs- of trekwerktuig)
* {{Z|he}}: {{W+|he|כננת}}
* {{Z|en}}: {{W+|en|winch}}, {{W-|en|differential windlass|guman=erê}}
* {{Z|no}}: {{W-|no|vinsj}}
* {{Z|pt}}: {{W+|pt|catraca}}
* {{Z|ru}}: {{W+|ru|лебёдка}}
* {{Z|sv}}: {{W+|sv|vinsch}}
* {{Z|tr}}: {{W+|tr|vinç}}
{{werger-bin}}
r4zvuuelqc24te70dvioaszyurdwkzz
qefa
0
43880
6534946
6465403
2026-09-20T18:16:30Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534946
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/qɛˈfɑː/}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[cih|Cih]]ê [[kevir]]î û [[vala]] di [[zinar]]an de.
#: {{hevmane|ku|şikeft|ziving|mixare|zang|berqef|qulqulî}}
#* {{jêder-websîte|ku|jêgirtin=Tu sitar û penaha fileyan nemabû. Serbazên tirk li dû wan dihatin, çeteyan jî mal û hebûnên wan talan dikirin. Demsal jî payîza dereng a Serhedê bû. Bi sedan jin û zar li '''qefa''' zanga kevirê sipî ji tirs û birsa û sermayê can didan. Ta niha jî hestiyên cenazên wan li '''qefa''' Kevirê Fila hene.|nivîskar=Perwîz Cîhanî|sernav=Mexîn?|websîte=Amidakurd.com|sal=8/2007}}
# {{kontrol|rêz=erê|[[mixer]]}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Bide ber ====
{{kol3|ku
|qaf
|qafe
}}
==== Jê ====
{{kol3|ku
|qefastan
|qefayî
}}
==== Bide Ber ====
{{kol3|ku
|çiya
|newal
|lat
|bîr
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|fi}}: {{W+|fi|luola}}
* {{Z|nl}}: {{W+|nl|hol}}, {{W+|nl|grot}}
* {{Z|en}}: {{W+|en|cave}}
* {{Z|tr}}: {{W+|tr|mağara}}, {{W+|tr|oyuk}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=aefq|faqe|feqa}}
ca7gyqcn73dqd3570czx8zh8p5mbesu
6535011
6534946
2026-09-20T20:12:24Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6535011
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/qɛˈfɑː/}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[cih|Cihê]] [[kevir]]î û [[vala]] di [[zinar]]an de.
#: {{hevmane|ku|şikeft|ziving|mixare|zang|berqef|qulqulî}}
#* {{jêder-websîte|ku|jêgirtin=Tu sitar û penaha fileyan nemabû. Serbazên tirk li dû wan dihatin, çeteyan jî mal û hebûnên wan talan dikirin. Demsal jî payîza dereng a Serhedê bû. Bi sedan jin û zar li '''qefa''' zanga kevirê sipî ji tirs û birsa û sermayê can didan. Ta niha jî hestiyên cenazên wan li '''qefa''' Kevirê Fila hene.|nivîskar=Perwîz Cîhanî|sernav=Mexîn?|websîte=Amidakurd.com|sal=8/2007}}
# {{kontrol|rêz=erê|[[mixer]]}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Bide ber ====
{{kol3|ku
|qaf
|qafe
}}
==== Jê ====
{{kol3|ku
|qefastan
|qefayî
}}
==== Bide Ber ====
{{kol3|ku
|çiya
|newal
|lat
|bîr
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|fi}}: {{W+|fi|luola}}
* {{Z|nl}}: {{W+|nl|hol}}, {{W+|nl|grot}}
* {{Z|en}}: {{W+|en|cave}}
* {{Z|tr}}: {{W+|tr|mağara}}, {{W+|tr|oyuk}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=aefq|faqe|feqa}}
trjpv3cm0csy5woij6txku8djqgclae
sexawet
0
45408
6534953
6467267
2026-09-20T18:26:41Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534953
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[merdî]], [[camêrî]], [[comerdî]], [[destvekirîtî]]
#* {{jêder|ku|jêgirtin=Ba '''sexawet''' ba diyanet ba hiner<br>Min li goşê lewhê qelbim kir xeter.|Cegerxwîn}}
# Tiştek [[belaş]] dan.
#: {{hevmane|ku|bexş|şabaş|hîbe|teberih|diyarî|hediye|berarî|bexşîn|xêrkirin}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
Ji {{deyn|ar|ku|سَخَاوَة}}.
==== Werger ====
{{werger-ser}}
* {{Z|fi}}: {{W+|fi|anteliaisuus}}
* {{Z|en}}: {{W+|en|generosity}}
* {{Z|tr}}: {{W+|tr|cömertlik}}, {{W-|tr|sehavet}}, {{W+|tr|sahavet}}, {{W+|tr|hibe}}
{{werger-bin}}
421nrj0f4zrv6vba9rtvnjzmg1t5czc
paşxan
0
45427
6534954
6467282
2026-09-20T18:27:59Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534954
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/pɑːʃˈxɑːn/}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# Tiştên li [[pişt]] yan [[derdor]]ên tiştekî.
#* {{jêder-websîte|ku|jêgirtin=Nûçegihînê/a ku wê bûyerê bike nûçe divê bi wî warî re têkildar be, li ser '''paşxan'''a bûyerê hinekî agahdar be.|nivîskar=Samî Tan|sernav=Nûçe, nûçegihan û neqandina nûçeyan|websîte=Azadiyawelat.com|sal=9/2007}}
# [[bûyer|Bûyer]]ên ku [[bandor]] li ser filan tiştî heye.
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|paşxane
|paşperde
}}
==== Etîmolojî ====
Ji {{qertaf|ku|paş-|xan}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Hintergrund|m}}
* {{Z|zh}}: {{W+|zh|背景}}
* {{Z|ar}}: {{W-|ar|خلفية}}
* {{Z|fa}}: {{W-|fa|دورنما|tr=dornuma}}, {{W-|fa|زمینه|tr=zemîne}}, {{W-|fa|مایه|tr=maye}}
* {{Z|fi}}: {{W+|fi|tausta}}
* {{Z|fr}}: {{W+|fr|d’arrière-plan}}
* {{Z|en}}: {{W+|en|background}}
* {{Z|it}}: {{W+|it|sfondo}}
* {{Z|hu}}: {{W+|hu|háttér}}
* {{Z|ru}}: {{W+|ru|фон}}
* {{Z|sv}}: {{W+|sv|bakgrund}}
* {{Z|tr}}: {{W+|tr|arka plan}}, {{W+|tr|artalan}}
{{werger-bin}}
ggygid2c9joole51hii25ms8spmzn56
6534955
6534954
2026-09-20T18:28:23Z
Wikihez
7344
/* Kurmancî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6534955
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/pɑːʃˈxɑːn/}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# Tiştên li [[pişt]] yan [[derdor]]ên tiştekî.
#* {{jêder-websîte|ku|jêgirtin=Nûçegihînê/a ku wê bûyerê bike nûçe divê bi wî warî re têkildar be, li ser '''paşxan'''a bûyerê hinekî agahdar be.|nivîskar=Samî Tan|sernav=Nûçe, nûçegihan û neqandina nûçeyan|websîte=Azadiyawelat.com|sal=9/2007}}
# [[bûyer|Bûyer]]ên ku [[bandor]] li ser filan tiştî heye.
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|paşxane
|paşperde
}}
==== Etîmolojî ====
Ji {{qertaf|ku|paş-|xan}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Hintergrund|m}}
* {{Z|zh}}: {{W+|zh|背景}}
* {{Z|ar}}: {{W-|ar|خلفية}}
* {{Z|fa}}: {{W-|fa|دورنما|tr=dornuma}}, {{W-|fa|زمینه|tr=zemîne}}, {{W-|fa|مایه|tr=maye}}
* {{Z|fi}}: {{W+|fi|tausta}}
* {{Z|fr}}: {{W+|fr|d’arrière-plan}}
* {{Z|en}}: {{W+|en|background}}
* {{Z|it}}: {{W+|it|sfondo}}
* {{Z|hu}}: {{W+|hu|háttér}}
* {{Z|ru}}: {{W+|ru|фон}}
* {{Z|sv}}: {{W+|sv|bakgrund}}
* {{Z|tr}}: {{W+|tr|arka plan}}, {{W+|tr|artalan}}
{{werger-bin}}
kn03f2f6tt2hh8zb1fsexh139h0gxih
6535010
6534955
2026-09-20T20:12:21Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6535010
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/pɑːʃˈxɑːn/}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# Tiştên li [[pişt]] yan [[derdor]]ên tiştekî.
#* {{jêder-websîte|ku|jêgirtin=Nûçegihînê/a ku wê bûyerê bike nûçe divê bi wî warî re têkildar be, li ser '''paşxan'''a bûyerê hinekî agahdar be.|nivîskar=Samî Tan|sernav=Nûçe, nûçegihan û neqandina nûçeyan|websîte=Azadiyawelat.com|sal=9/2007}}
# [[bûyer|Bûyerên]] ku [[bandor]] li ser filan tiştî heye.
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|paşxane
|paşperde
}}
==== Etîmolojî ====
Ji {{qertaf|ku|paş-|xan}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Hintergrund|m}}
* {{Z|zh}}: {{W+|zh|背景}}
* {{Z|ar}}: {{W-|ar|خلفية}}
* {{Z|fa}}: {{W-|fa|دورنما|tr=dornuma}}, {{W-|fa|زمینه|tr=zemîne}}, {{W-|fa|مایه|tr=maye}}
* {{Z|fi}}: {{W+|fi|tausta}}
* {{Z|fr}}: {{W+|fr|d’arrière-plan}}
* {{Z|en}}: {{W+|en|background}}
* {{Z|it}}: {{W+|it|sfondo}}
* {{Z|hu}}: {{W+|hu|háttér}}
* {{Z|ru}}: {{W+|ru|фон}}
* {{Z|sv}}: {{W+|sv|bakgrund}}
* {{Z|tr}}: {{W+|tr|arka plan}}, {{W+|tr|artalan}}
{{werger-bin}}
2p23qw1nleomjftm7mpey7ks3bwtkez
lîlan
0
45869
6534944
6467694
2026-09-20T18:07:17Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534944
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/liːˈlɑːn/}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[tilîlî]], [[zilxit]], [[fîtî]]
#* {{jêder|ku|sernavê nivîske Abdurrahman Durre di Kerkuk-kurdistan.com de, 8/2005|jêgirtin=Govend û Dîlan, sazbend û '''lîlan'''}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|tr}}: {{W|tr|zılgıt}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=aîlln|lalîn}}
rxm7g3heasm9qo6ur4ulh1ogivfoly3
branş
0
46250
6534926
6468141
2026-09-20T17:43:35Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534926
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[ta]], [[hêl]], [[rex]], [[çeq]], [[liq]], [[sektor]], [[seksiyon]], [[kert]]
#* {{jêder|ku|Krg.org, 10/2007|jêgirtin=Bi armanca pêşxistina '''branşa''' zîraetê, Abdulezîz Teyb bi şandeyeke Îspaniyayê re civiya}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
Ji îngilîzî [[branch]]
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Branche|f|guman=erê}}
* {{Z|tr}}: {{W+|tr|seksiyon}}, {{W+|tr|branş}}
{{werger-bin}}
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# branş, [[liq]], [[çiqil]], [[şax]]
7c3us3pva77q2adti5v2v34omyij7gl
kelîk
0
46751
6535038
6468776
2026-09-21T04:34:58Z
~2026-51052-28
15389
Werger: +almanî : [[Erbse (grüne]] (bi alîkarî)
6535038
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[riwek|Riwekek]] e ku [[lib]]ên wek yên [[nok]]an dide.
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
* [[kelî]]
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Bide ber ====
* [[kelik]]
* [[kêlîk]]
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W-|de|Erbse (grüne|m}}
* {{Z|ar}}:{{W|ar|الجلبان|guman=erê}}
* {{Z|en}}: {{W|en|green pea|guman=erê}}
{{werger-bin}}
{{mijar|ku|riwek}}
==== Anagram ====
* {{anagram|ku|a=eîkkl|kelkî|lîkek}}
qbi0pl8op8tmuakl4fvxqfmg30nsanu
fon
0
46981
6534992
6468985
2026-09-20T19:33:31Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534992
wikitext
text/x-wiki
{{h-bnr|fön}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[kom]]ek [[pare]] anku [[dirav]]
# [[weqf]], [[dezgeh]]a [[xêrxwaz]] ya ku [[pare]] dide hin kesan yan [[proje]]yan
#* {{jêder|ku|Krg.org, 11/2007|jêgirtin=Komisyona qanûnî ya perlemantoya Kurdistanê li gel Çinar Seîda wezîra şehîdan û karûbarên enfalkiriyan civiya û projeqanûna '''Fonda''' (Sindûqa) Hevkariyê ya bo malbatên şehîdan û Qurbanên Jenosîdê gotûbêj kir.}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|fond
}}
==== Etîmolojî ====
Ji {{deyn|fr|ku|fond}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Fond|m}}, {{W|de|Fund|guman=erê}}
* {{Z|fi}}: {{W+|fi|rahasto}}
* {{Z|fr}}: {{W+|fr|fond}}
* {{Z|en}}: {{W+|en|fund}}, {{W|en|phon|guman=erê}}
* {{Z|it}}: {{W+|it|fondo}}, {{W+|it|fondo}}
* {{Z|tr}}: {{W+|tr|fon}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=fno|nof}}
== {{ziman|ht}} ==
=== Navdêr ===
{{navdêr|ht}}
# {{mijar|ht|erdnîgarî}} {{g|ku|gelî}}, {{g|ku|newal}}, {{g|ku|nihal}}
=== Çavkanî ===
{{rûpela wergerê|ht|gelî}}
rb02ggct9fi24z6c4emceq6msvlqfzf
rewêştin
0
47221
6534936
6469136
2026-09-20T17:58:21Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534936
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Lêker ===
{{lêker|ku|c=}}
# [[nikirandin]], [[kêlan]], [[kêlandin]], [[kolan]], [[kolandin]]
#* {{jêder-websîte|ku|jêgirtin=Bi rastî ev bêhtirî deh salan e ku min hîn helbestin wisa xweş û têrwate û nazik û hêsan bi kurmancî nexwendine. Helbestin ku deqên herî kevnar di wêjeyên mirovayetiyê de tînin bîra mirov. Helbestin ku sirûda sirûdan a Silêman di Tewratê de, nivîsên ku li ser kevirên Sumeriyan û Asûriyan hatine '''rewêştin''' tînin bîra mirov.|nivîskar=Jan Dost|sernav=Dizê [[agir|êgir]]: Arjen Arî|websîte=Diyarname.com|sal=12/2007}}
==== Tewandin ====
:-rewêj-
:-rewê-
==== Formên alternatîv ====
{{kol3|ku
|rawêjtin
|rawêştin
|rewaştin
|rewajtin
|rewêjtin
}}
==== Jê ====
{{kol3|ku
|rewêjer
|rewêştî
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|en}}: {{W+|en|carving}}, {{W+|en|digging}}
* {{Z|tr}}: {{W+|tr|kazıma}}, {{W+|tr|kazımak}}
{{werger-bin}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# {{mane?|ku}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=eêinrştw|rewiştên|rişwetên}}
h44h9mss0cyz99d5r4q27rkifmshrym
xwest
0
47715
6534918
6320800
2026-09-20T16:43:10Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534918
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[daxwaz]], [[xwestek]], [[doz]], [[dawa]], [[vîn]], [[wîst]]
#* {{jêder-websîte|ku|jêgirtin=Herwisan min bi salan ji bo zaravê kurmancî bi Soranîaxêvan ra gengeşe danîye. Me li ser wê mijarê nivîsîye. Lê me serên hevdu wekî ku tu dibêjî neişkandîye. Me bi bêhna fireh û bi berbihêrî û bi şêweyek zanistî ji hevdu ra axiftîye û me rêz ji bo bîr û boçûnên hevdu girtîye. Heryekê ji me bi hanîna pelgeyên pêdivî têdikoşî ku em '''xwasta''' xwe bi cî bînin. Lê qet şerr û serişkandin li nîveka me da dernediket.|nivîskar=Perwîz Cîhanî|sernav=Karîn yan kanîn?|websîte=Amidakurd.com|sal=12/2007}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|xwastin
}}
==== Jê ====
{{kol3|ku
|xwestî
}}
==== Etîmolojî ====
[[xwestin]]
=== Formekê lêkerê ===
{{lêker|ku|form=erê}}
# [[dema borî]] ji lêkera '''[[xwestin]]'''
gth5t5ttqapww7tzd1ga56rb8s5p173
metamorfoz
0
50191
6534923
6472267
2026-09-20T17:42:19Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534923
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[mirhov]], [[teşabet]], [[îbret]], [[guherîn]], [[transformasyon]] (bi taybetî di [[çîrok]]an de dema ku mirovek dibe [[heywan]]ek yan [[riwek]]ek)
#* {{jêder|ku|Louis Aragon: ''Sorgul û Xamekgul'', wergerandin: Husein Muhammed, Kovara MEHNAME, jêgirtin: 7/2008|jêgirtin=Ax meha gulan meha '''metamorfosan'''<br>gulana bêewr pûşbera şeqkirî<br>ji bîr nakim sorgulan xamekgulan na<br>ne wan yên hatin veşartin bin cilên biharê}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
Bi rêya {{hv|fr|métamorphose}} ji yewnaniya kevnar [[meta-]] + [[morfoz]]
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Metamorphose|f}}, {{W|de|Verwandlung|f|guman=erê}}
* {{Z|eo}}: {{W+|eo|metamorfozo}}
* {{Z|fi}}: {{W+|fi|metamorfoosi}}, {{W+|fi|muodonmuutos}}
* {{Z|fr}}: {{W+|fr|métamorphose}}
* {{Z|nl}}: {{W+|nl|gedaanteverwisseling}}, {{W+|nl|metamorfose}}
* {{Z|io}}: {{W+|io|metamorfoso}}
* {{Z|en}}: {{W+|en|metamorphosis}}, {{W+|en|recreation}}, {{W+|en|transformation}}
* {{Z|es}}: {{W+|es|metamorfosis}}
* {{Z|tr}}: {{W+|tr|başkalaşma}}, {{W+|tr|metamorfoz}}
{{werger-bin}}
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# [[teşeguhirîbûn]], [[dirûvguhirîbûn]], metamorfoz
hlsjyi8p1qoaclocvef759i4mqwn72b
dahên
0
50296
6534925
6472372
2026-09-20T17:43:19Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534925
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[îcad]], [[keşf]], tiştê ku kesek cara yekem çêdike
#* {{jêder-websîte|ku|jêgirtin=Cezîrî '''dahênên''' (îcatên) xwe di karên rojane de, di qesra hikumdarên Artukiyan de bi kar anîne.|nivîskar=A. Balî|sernav=Zanyarê kurd, bavê robot û kompûterê Îsmaîlê Cezîrî, 1136 - 1206|websîte=Amidakurd.com|sal=havîn 2008}}
# {{kontrol|rêz=erê|[[danîn]]}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Jê ====
{{kol3|ku
|dahêner
|dahênerî
}}
==== Etîmolojî ====
[[dahênan]]
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W-|af|bedenksel}}
* {{Z|de}}: {{W+|de|Erfindung|f}}
* {{Z|da}}: {{W-|da|opfindelse}}
* {{Z|eo}}: {{W+|eo|invento}}, {{W-|eo|inventaĵo}}
* {{Z|fo}}: {{W-|fo|uppfinning}}
* {{Z|fi}}: {{W+|fi|keksintö}}
* {{Z|fr}}: {{W+|fr|invention}}
* {{Z|io}}: {{W+|io|invento}}
* {{Z|en}}: {{W+|en|invention}}
* {{Z|no}}: {{W-|no|oppfinnelse}}
* {{Z|nl}} {{W+|nl|uitvinding}}
* {{Z|pap}}: {{W-|pap|invenshon}}
* {{Z|pt}}: {{W+|pt|invenção}}, {{W-|pt|invento}}
* {{Z|sv}}: {{W+|sv|uppfinning}}
* {{Z|tr}}: {{W+|tr|icat}}, {{W+|tr|bulgu}}, {{W+|tr|buluş}}, {{W+|tr|keşif}}
{{werger-bin}}
sq6k98l4ifkziaiqoho0hbrjyaew5b4
formalîte
0
50298
6534948
6472373
2026-09-20T18:17:02Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534948
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[resmiyet]], [[fermîtî]], [[konvensiyonelî]], [[resmîtî]], [[formalbûn]], [[formalî]], [[formalîtî]], [[formaltî]]
#* {{jêder|ku|jêgirtin=Taha Berwarî radigihîne ku ji hin waran de wezaret diviya têkeve nav kar, mesela miraceatê, '''formalîteyan''' peywendiyên dîplomasî, vize û seferê û hwd. Wekî din jî werziş li Kurdistanê li ser navê Kurdistanê gelek nuh e, gelek bêtecrube ye, mihtacî rêberiyeka modern a xwedan vizyon e da karibe di karûbarên xwe de rêkeftî û serkeftî be û ber bi muesesetiyeka modern ve biçe, loma ew vî karî wek misyonekê ji xwe re dibîne, amade ye xwe bide ber xurdekariyan û karên rojane jî bi şertê ku midaxeleyî nav karûbarên dezgeh û rêxistinên din nebe û wan ber bi sistî û hêvîdariya jiderveyî xwe de nebe.|Netkurd.com, 7/2008}}
==== Tewandin ====
{{ku-tewîn-nav|mê|guherto=hemû}}
==== Etîmolojî ====
Ji {{ji|fr|ku|formalité}} ji [[formel]] + [[-ité]]
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Formalität}} {{mê}}, {{W|de|Äusserlichkeit|guman=erê}} {{mê}}, {{W|de|Formalie|f|guman=erê}}
* {{Z|cs}}: {{W|cs|formalita}} {{mê}}
* {{Z|fi}}: {{W|fi|muodollisuus}}
* {{Z|fr}}: {{W|fr|formalité}}
* {{Z|nl}}: {{W|nl|formaliteit}} {{mê}}
* {{Z|en}}: {{W|en|formality}}
* {{Z|pt}}: {{W|pt|formalidade}}
* {{Z|sv}}: {{W|sv|formalitet}}
* {{Z|tr}}: {{W|tr|formalite}}
{{werger-bin}}
bmqne94v12evfydcr8bpav6sa6279dg
dirêşk
0
50403
6534929
6472448
2026-09-20T17:53:48Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534929
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[derzî]], [[dirêzî]], [[şûjin]]
#* {{jêder|ku|Îbrahîm Remezan Zaxoyî: ''Ferhenga Pîşkên Kurdî'', weşanxaneya Spîrêz, Dihok, 2007, r. 60|jêgirtin=Pînedozî '''dirêşk''' daçeqand çermî.}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Nadel|f}}
* {{Z|da}}: {{W+|da|nål}}
* {{Z|ar}}: {{W+|ar|ابرة}}, {{W+|ar|مهماز}}, {{W+|ar|منخس}}, {{W|ar|المخرز|guman=erê}}
* {{Z|hy}}: {{W+|hy|ասեղ}}
* {{Z|eo}}: {{W+|eo|nadlo}}, {{W+|eo|kudrilo}}
* {{Z|fa}}: {{W-|fa|سوزن|tr=sûzen}}
* {{Z|fo}}: {{W+|fo|nál}}
* {{Z|fi}}: {{W+|fi|neula}}
* {{Z|fr}}: {{W+|fr|aiguille}}
* {{Z|fy}}: {{W+|fy|nuddel}}, {{W+|fy|nulle}}
* {{Z|gd}}: {{W+|gd|snathad}}
* {{Z|nl}}: {{W+|nl|naald}}, {{W+|nl|kompasnaald}}, {{W+|nl|wijzer}}
* {{Z|io}}: {{W+|io|agulo}}
* {{Z|en}}: {{W+|en|needle}}, {{W+|en|bodkin}}, {{W+|en|prod}}, {{W+|en|goad}}
* {{Z|it}}: {{W+|it|ago}}
* {{Z|ca}}: {{W+|ca|agulla}}, {{W+|ca|pua}}
* {{Z|la}}: {{W+|la|acus}}
* {{Z|ms}}: {{W+|ms|jarum}}
* {{Z|yua}}: {{W+|yua|puuts’}}
* {{Z|hu}}: {{W+|hu|tű}}
* {{Z|no}}: {{W+|no|nål}}
* {{Z|pap}}: {{W+|pap|angua}}, {{W+|pap|hangua}}
* {{Z|pt}}: {{W+|pt|agulha}}
* {{Z|es}}: {{W+|es|aguja}}, {{W+|es|aguja de coser}}
* {{Z|sw}}: {{W+|sw|sindano}}
* {{Z|sv}}: {{W+|sv|nål}}
* {{Z|tl}}: {{W+|tl|karáyom}}
* {{Z|tr}}: {{W+|tr|iğne}}, {{W+|tr|biz}}
{{werger-bin}}
szj8ahedg3z88fdvtcahrdfwq29gdrf
konsept
0
50470
6534949
6472493
2026-09-20T18:17:17Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534949
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[têgih]], [[terîm]], [[waje]], [[mezmûn]], [[islah]], [[wate]], [[mefhûm]]
# [[raman]], [[fikir]], [[îdea]]
#* {{jêder|ku|jêgirtin=Min dixwest di vê albûma xwe de, kilameke ku min ji dayîka xwe wergirtiye, bistirêm. Lê belê nebû qismet. Bi hêvî me careke din ezê bistirêm. Sedema ku niha di vê albûmê de cih negirtin, cudabûna '''konsepta''' albûma me bû...|Ayfer Duztaş di hevpeyivînekê de li gel Salihê Kevirbirî, Netkurd.com, 7/2008}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|konsêpt
}}
==== Etîmolojî ====
Bi rêya fransî yan îngilîzî [[concept]] ji latînî
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Konzept|n}}, {{W|de|Begriff|m}}
* {{Z|cs}}: {{W|cs|pojem|m}}
* {{Z|zh}}: {{W+|zh|概念}}
* {{Z|fi}}: {{W+|fi|käsite}}, {{W+|fi|konsepti}}
* {{Z|fr}}: {{W|fr|concept|m}}
* {{Z|nl}}: {{W+|nl|opvatting}}, {{W+|nl|begrip}}
* {{Z|he}}: {{W-|he|מושג|tr=musag}}
* {{Z|en}}: {{W+|en|concept}}
* {{Z|ia}}: {{W+|ia|concepto}}
* {{Z|it}}: {{W|it|concetto|m}}
* {{Z|ja}}: {{W-|ja|概念|tr=gainen}}
* {{Z|pt}}: {{W|pt|conceito|m}}
* {{Z|es}}: {{W|es|concepto|m}}
* {{Z|sv}}: {{W+|sv|koncept}}
* {{Z|tr}}: {{W+|tr|konsept}}, {{W+|tr|kavram}}
* {{Z|cy}}: {{W|cy|syniad|m}}
{{werger-bin}}
eq0xmqhszk79n5gttovkcpoh5q9gui0
recim
0
50589
6534950
6440368
2026-09-20T18:18:42Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534950
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Girolamo da Treviso cat01.jpg|thumb|recimkirin]]
{{navdêr|ku|z=m}}
# [[kevir|Kevir]] û [[ber]] li kesekî [[ceza]]kirî dan daku ew bêt [[kuştin]], bi [[kevir]] û [[ber]]an [[kuştin]] (yan [[êşandin]]).
#* {{jêder|ku|jêgirtin=Li Îranê cezayê '''recmê''' piranî ji bo cezayên zînayê tê dayîn. Mêr heta piştê û jin jî heta sîngê tê binaxkirin û piştre bi keviran lê wan tê dayîn. <br>Lê divê kevir ne pirr mezin bin da ku ew kes [zû] nemire.|Netkurd.com, 7/2008}}
# {{f|ku|mecazî}} bi [[dijwar]]î [[nefret]] kirin
==== Formên alternatîv ====
{{kol3|ku
|recm
}}
==== Jê ====
{{kol3|ku
|recimandin
|recimandî
|recimî
|recimîn
|recimîner
|recimker
|recmî
}}
==== Etîmolojî ====
Ji {{ji|ar|ku|رجم|tr=recim}}, hevreha {{hv|arc|רגמ|tr=regamu}}
==== Bide ber ====
{{kol3|ku
|recîm
}}
==== Werger ====
{{werger-ser|bi [[kevir]] û [[ber]]an [[kuştin]]}}
* {{Z|de}}: {{W-|de|Steiningung}}
* {{Z|zh}}: {{W+|zh|石刑}}
* {{Z|da}}: {{W+|da|stening}}
* {{Z|ar}}: {{W-|ar|رجم|tr=recm}}
* {{Z|fa}}: {{W-|fa|سنگسار|tr=sengsar}}
* {{Z|fi}}: {{W+|fi|kivitys}}, {{W-|fi|kivitystuomio}}
* {{Z|fr}}: {{W-|fr|laptidation}}
* {{Z|nl}}: {{W+|nl|steniging}}
* {{Z|en}}: {{W-|en|laptidation}}, {{W+|en|stoning}}
* {{Z|it}}: {{W+|it|lapidazione}}
* {{Z|ja}}: [[石打ち]] (いしうち)
* {{Z|ko}}: {{W-|ko|투석형}}
* {{Z|no}}: {{W-|no|steining}}
* {{Z|pl}}: {{W+|pl|ukamienowanie}}
* {{Z|pt}}: {{W-|pt|lapidação}}
* {{Z|ro}}: {{W+|ro|lapidare}}
* {{Z|ru}}: {{W-|ru|побие́ние камня́ми}}
* {{Z|es}}: {{W+|es|lapidación}}
* {{Z|sv}}: {{W+|sv|stening}}
* {{Z|tr}}: {{W+|tr|recm}}
{{werger-bin}}
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# [[recm]], [[ricm]]
==== Anagram ====
* {{anagram|tr|a=ceimr|merci}}
0fdu7568zgu2z3e6sd3a7uk6lkhgc67
istila
0
50761
6534951
6472695
2026-09-20T18:20:19Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534951
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[dagir]]kirin, [[vegirtin]], [[firstiqandin]], [[fetih]]kirin, [[îşgal]]kirin
#* {{jêder|ku|jêgirtin=Her çi qasî ev roman ji terefê weşanxaneya Pêriyê ve di pêşgotina wê de (wek notekê, ne wek pêşgotinekê) ew wek romaneke dîroka Mêrdînê hesibandibe jî û teva ko ev roman li ser esasê dîroka Mêrdînê hatibe avakirin, di eslê xwe de ew li cografiyeke berfirek bela bûye û ji hêla bûyer û '''îstilayan''' ve ne bi Mêrdînê tenê sînorkirî ye. Ew bi naveroka xwe, bi siyaset û şerên baweriyên îqtidarên cûda ve û bi dijayetî û entrîqayên mîsyonerên dema xwe ve ko wan dixwest li hemî deverên mintîqeyê bi awayekî berfirehî hukimî bikin, ew bibin xwediyê îdareyeke mezin ya ol, mezheb û her weha desthilatên wan medeniyetên wê demê yên nola Bîzans (Yûnanî), Suryan, Ereb, Kildan, Yahûda, Kurd û Farizan ko ji bo baweriyên xwe, desthilat, talan û qebûlkirna kitêb û felsefeyên xwe şerên mezin, finêsên genî li wê cografiyayê pêk anîne ko ji bajarê qedîm Mêrdînê û axa wê wêdetir in... Di nav tevna vê vegotinê de, mirov pê dihese ko mesele bi bajarê Mêrdînê tenê de nema ye û ew xwe digîhîne heta bi Mûsil, Amûd û Serê Kaniyê jî.|Enwer Karahan: ''Romaneke mûhteşem: Hêlîna qertelî", li ser romana Bülent Tekin "Kartal Yuvası", Nefel.com, 10/2008}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|istîla
|îstila
|îstîla
}}
==== Etîmolojî ====
{{ji?|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|fi}}: {{W+|fi|miehitys}}
* {{Z|en}}: {{W+|en|invasion}}, {{W+|en|occupation}}
* {{Z|tr}}: {{W+|tr|istila}}, {{W+|tr|işgal}}, {{W+|tr|salgın}}, {{W+|tr|kaplama|guman=erê}}
{{werger-bin}}
== {{ziman|akz}} ==
=== Rengdêr ===
{{rengdêr|akz}}
# [[anîn]]
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# [[vegirî]], [[dagirî]], [[leyan]]
# [[girtin]], [[ziftkirin]], [[lêbelabûn]]
5jscyrurargasaczox1tco4zs504j8h
6534952
6534951
2026-09-20T18:24:46Z
Wikihez
7344
/* Navdêr */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6534952
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[dagir]]kirin, [[vegirtin]], [[firstiqandin]], [[fetih]]kirin, [[îşgal]]kirin
#* {{jêder|ku|jêgirtin=Her çi qasî ev roman ji terefê weşanxaneya Pêriyê ve di pêşgotina wê de (wek notekê, ne wek pêşgotinekê) ew wek romaneke dîroka Mêrdînê hesibandibe jî û teva ko ev roman li ser esasê dîroka Mêrdînê hatibe avakirin, di eslê xwe de ew li cografiyeke berfirek bela bûye û ji hêla bûyer û '''îstilayan''' ve ne bi Mêrdînê tenê sînorkirî ye. Ew bi naveroka xwe, bi siyaset û şerên baweriyên îqtidarên cûda ve û bi dijayetî û entrîqayên mîsyonerên dema xwe ve ko wan dixwest li hemî deverên mintîqeyê bi awayekî berfirehî hukimî bikin, ew bibin xwediyê îdareyeke mezin ya ol, mezheb û her weha desthilatên wan medeniyetên wê demê yên nola Bîzans (Yûnanî), Suryan, Ereb, Kildan, Yahûda, Kurd û Farizan ko ji bo baweriyên xwe, desthilat, talan û qebûlkirna kitêb û felsefeyên xwe şerên mezin, finêsên genî li wê cografiyayê pêk anîne ko ji bajarê qedîm Mêrdînê û axa wê wêdetir in... Di nav tevna vê vegotinê de, mirov pê dihese ko mesele bi bajarê Mêrdînê tenê de nema ye û ew xwe digîhîne heta bi Mûsil, Amûd û Serê Kaniyê jî.|Enwer Karahan: ''Romaneke mûhteşem: Hêlîna qertelî'', li ser romana Bülent Tekin "Kartal Yuvası", Nefel.com, 10/2008}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|istîla
|îstila
|îstîla
}}
==== Etîmolojî ====
{{ji?|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|fi}}: {{W+|fi|miehitys}}
* {{Z|en}}: {{W+|en|invasion}}, {{W+|en|occupation}}
* {{Z|tr}}: {{W+|tr|istila}}, {{W+|tr|işgal}}, {{W+|tr|salgın}}, {{W+|tr|kaplama|guman=erê}}
{{werger-bin}}
== {{ziman|akz}} ==
=== Rengdêr ===
{{rengdêr|akz}}
# [[anîn]]
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# [[vegirî]], [[dagirî]], [[leyan]]
# [[girtin]], [[ziftkirin]], [[lêbelabûn]]
jbymlkccinuh0iyp6l7585gwnktq29v
kulîs
0
51697
6534933
6473506
2026-09-20T17:56:22Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534933
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[perde]]yên yan [[nexş]]ên ku di [[şano]]yan de tên [[bikaranîn]]
# {{f|ku|mecazî}} [[nepenî]], ne [[berçav]]
#* {{jêder-websîte|ku|jêgirtin=Her ji niho ve li '''kulîsên''' Parlamentoya Tirkiyeyê û di civînên siyasî yên li Enqereyê de minaqeşeya perwerdeya bi zimanê kurdî li dibistanan têt kirin û ne dûr e ko heta saleka dî rê ji wê re jî vebe. Jixwe ji niho ve hinek unîversîteyan dest bi avakirina beşên kurdî kiriye û di sewiyeya akademîk de jî kurdî dê bibe xudan xwedî.|nivîskar=Arif Zêrevan|sernav=Dema ko li bakur biqewime, pir mezin û ho baş jî diqewime|websîte=Nefel.com|sal=2/2009}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Jê ====
{{kol3|ku
|kulîs kirin<crz:lêker>
|kulîskirin<crz:navdêr>
}}
==== Etîmolojî ====
Ji {{deyn|fr|ku|coulisse}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Kulisse|f|guman=erê}}
* {{Z|fa}}: {{W+|fa|پشت پرده|guman=erê}}, {{W|fa|محافل مخفی|guman=erê}}
* {{Z|fi}}: {{W+|fi|kulissi}}
* {{Z|fr}}: {{W+|fr|coulisse}}
* {{Z|en}}: {{W+|en|coulisse}}, {{W+|en|backstage}}
* {{Z|tr}}: {{W+|tr|kulis}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=îklsu|luksî}}
fcun0m737kc0sn34th590ajnsjzp5qw
baxox
0
51821
6534934
6473611
2026-09-20T17:57:13Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534934
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:CentaureaCyanus-bloem-kl.jpg|thumb|250px|Baxoxek]]
{{navdêr|ku|z=m}}
# [[hin|Hin]] [[cûn]]ên [[gul]]an in.
#: {{hevmane|ku|angelok|kanêje}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Navê zanistî ====
* Centaurea (Centaurea kurdica)
==== Jê ====
{{kol3|ku
|baxoxistan
|baxoxistanî
|baxoxî
|baxoxnas
|baxoxnasî
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Kornblume|f}}, {{W-|de|Kornflockenblume}}, {{W+|de|Zyane}}
* {{Z|eo}}: {{W-|eo|cejano}}
* {{Z|fy}}: {{W-|fy|roggeblom}}
* {{Z|nl}}: {{W+|nl|korenbloem}}
* {{Z|io}}: {{W+|io|aciano}}
* {{Z|en}}: {{W+|en|cornflower}}
* {{Z|pt}}: {{W-|pt|aciano}}
* {{Z|ru}}: {{W+|ru|василек}}
* {{Z|es}}: {{W+|es|aciano}}, {{W+|es|azulejo}}
* {{Z|sv}}: {{W+|sv|blåklint}}, {{W+|sv|klint}}
* {{Z|tr}}: {{W-|tr|Peygamber çiçegi}}, {{W-|tr|mavi kataron}}
{{werger-bin}}
clwtv3g3sagj4zww84sc93gv9wvxyyi
duşes
0
52637
6534935
6474398
2026-09-20T17:57:58Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534935
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[duk|Duk]]a [[jin]]; [[jin]]a [[duk]]ekî.
#: {{hevmane|ku|xatûn|dukbanî}}
#* {{jêder|ku|jêgirtin='''Duşesa''' Sosret, Tişta Kej: Sylvia Plath|sernavê nivîsarek [[w:Kawa Nemir|Kawa Nemir]], ''[[w:Kovara W|Kovara W]]'', 3/2009}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Jê ====
{{kol3|ku
|duşesane
|duşesî
|duşesîtî
|duşestî
}}
==== Etîmolojî ====
Bi {{hv|fr|duchesse}} ji [[duc]] + [[-esse]] ji {{hv|la|dux}}
==== Bide ber ====
{{kol3|ku
|duşeş
}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Herzogin|f}}
* {{Z|cs}}: {{W|cs|vévodkyně|f}}
* {{Z|fi}}: {{W+|fi|herttuatar}}
* {{Z|fr}}: {{W|fr|duchesse|f}}
* {{Z|gd}}: {{W|gd|ban-diùc|f}}
* {{Z|nl}}: {{W|nl|hertogin|f}}
* {{Z|en}}: {{W+|en|duchess}}
* {{Z|ga}}: {{W|ga|bandiúc|f}}
* {{Z|it}}: {{W+|it|duchessa}}
* {{Z|mk}}: {{W|mk|војвотка|tr=vójvotka|f}}
* {{Z|hu}}: {{W+|hu|hercegné}}
* {{Z|ru}}: {{W|ru|герцогиня|tr=g'ercogín'a|f}}
* {{Z|es}}: {{W|es|duquesa|f}}
* {{Z|sv}}: {{W|sv|hertiginna|c}}
* {{Z|tr}}: {{W+|tr|düşes}}
{{werger-bin}}
ksqyran1kd1lqvkoumlrnv1cx3wa3pe
6534979
6534935
2026-09-20T19:12:23Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6534979
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[duk|Duka]] [[jin]]; [[jin]]a [[duk]]ekî.
#: {{hevmane|ku|xatûn|dukbanî}}
#* {{jêder|ku|jêgirtin='''Duşesa''' Sosret, Tişta Kej: Sylvia Plath|sernavê nivîsarek [[w:Kawa Nemir|Kawa Nemir]], ''[[w:Kovara W|Kovara W]]'', 3/2009}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Jê ====
{{kol3|ku
|duşesane
|duşesî
|duşesîtî
|duşestî
}}
==== Etîmolojî ====
Bi {{hv|fr|duchesse}} ji [[duc]] + [[-esse]] ji {{hv|la|dux}}
==== Bide ber ====
{{kol3|ku
|duşeş
}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Herzogin|f}}
* {{Z|cs}}: {{W|cs|vévodkyně|f}}
* {{Z|fi}}: {{W+|fi|herttuatar}}
* {{Z|fr}}: {{W|fr|duchesse|f}}
* {{Z|gd}}: {{W|gd|ban-diùc|f}}
* {{Z|nl}}: {{W|nl|hertogin|f}}
* {{Z|en}}: {{W+|en|duchess}}
* {{Z|ga}}: {{W|ga|bandiúc|f}}
* {{Z|it}}: {{W+|it|duchessa}}
* {{Z|mk}}: {{W|mk|војвотка|tr=vójvotka|f}}
* {{Z|hu}}: {{W+|hu|hercegné}}
* {{Z|ru}}: {{W|ru|герцогиня|tr=g'ercogín'a|f}}
* {{Z|es}}: {{W|es|duquesa|f}}
* {{Z|sv}}: {{W|sv|hertiginna|c}}
* {{Z|tr}}: {{W+|tr|düşes}}
{{werger-bin}}
2eqejq3gb43dss7hzfgk5syfc4mub7c
hummer
0
52935
6534932
6384042
2026-09-20T17:55:54Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534932
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:2006 Hummer H3 H1 and H2.jpg|thumb|250px|çend hummer]]
{{navdêr|ku|z=m}}
# Hin [[erebe]]yên wek [[jîp]]an in.
#* {{jêder|ku|jêgirtin=Polîsê kanadî derhal birêket, hat bi çek û cebirxane dora wan girt. Lê ne çek dît, ne jî terrorist. Ji dêlva wê ve kamera hebûn û kesên ku film dikêşan. '''Hummereka''' reş jî ku Şivan ji kurdekî li Kanadayê deyn kirbû jî li ba wan bû. Tevî vê jî polîs bi tawana tîma teorîst ew girtin li erdê dirêjkirin, destên wan xistin kelemçeyan. lê dîtin ku şaşîtî kirine uzir xwstin, ew berdan.|Netkurd.com, 8/2009}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|Hummer
}}
==== Têkildar ====
{{kol3|ku
|humvî
}}
==== Etîmolojî ====
Deyn ji {{deyn|en}}.
==== Anagram ====
* {{anagram|ku|a=ehmmru|mehrum}}
gxz5z2iknwxv6hu0c1c6cya06ffsoqv
zebir
0
57851
6534947
6368071
2026-09-20T18:16:54Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534947
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[zilm]], [[zordarî]], [[stem]], [[neheqî]], [[xedre]], [[tepeserî]], [[çewisandin]]
#* {{jêder-websîte|ku|jêgirtin=Divê em ji bîr nekin ko bi kêmanî ji sedê heftê û pênc ji gelê kurd heta niha li bin '''zebir''' û zora dijminan dinale. Divê birêz Barzanî bi vê nîvazadbûna Kurdsitana başûr qane nebe heta ku armanca stratejîk ya gelê kurd neyêt cih, xebat berdewam be. Hîna jî rêyeke dûr û pirrî biasteng li pêşiya gelê kurd e. Herçend ku ev jî erkê hemû kurdan e, lê cardî jî barê girantir dikeve ser milê serokê Kurdistanê birêz Mesûd Barzanî.|nivîskar=Mihemed Hacî|sernav=Retorîka di peyivên serok Barzanî de|websîte=Nefel.com|sal=12/2010}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|zebr
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Bide ber ====
{{kol3|ku
|sebir
|Zebûr
}}
==== Werger ====
{{werger-ser}}
* {{Z|en}}: {{W+|en|oppression}}, {{W+|en|vehemence}}, {{W+|en|severity}}, {{W+|en|sternness}}, {{W+|en|birr}}, {{W+|en|austerity|guman=erê}}, {{W+|en|intension|guman=erê}}
* {{Z|tr}}: {{W+|tr|şiddet}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=beirz|beriz|rizbe|ziber}}
q3lcgd4y72qzbxyzsg5k784hkjc2dml
lîmît
0
60452
6534978
6311451
2026-09-20T19:00:32Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534978
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[sinor]], [[hed]], [[tixûb]], [[hidûd]]
#* {{jêder|ku|jêgirtin=Me yekemîn herêma sewalvaniyê ya organîze ava kir. Vê mehê dê bikeve meriyetê. Dê 102 heb bên avakirin. Ev tesîs dê bikaribin ji fonên Yekîtiya Ewropayê ji sedî 65 hîbe werbigirin. '''Lîmîta''' wê 1 milyon euro ye. Yên ji 40 salî biçûktir dê bikaribin ji sedî 65 piştgirî bigirin. Ger di ser 40 salî re be û mêr be, dê ji sedî 60 piştgirî werbigire.|Wezîrê Çandiniyê yê Tirkiyê û parlamenterê Diyarbekirê yê AK Partiyê Mehdî Eker di hevpeyivînekê de li gel Bawer Çem, Rudaw.net, 5/2011}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
Ji {{deyn|en|ku|limit}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Deckelung|f|guman=erê}}
{{werger-bin}}
1qr1gt2txa4tknyvta06igto622nlck
kersex
0
62503
6534989
6326857
2026-09-20T19:30:02Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534989
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Squeaky Beach-Wilsons Prom-Vic.JPG|thumb|250px|kersexek]]
{{navdêr|ku|z=m}}
# [[cih|Cih]]ê li [[ber]] [[av]]ê.
#: {{hevmane|ku|berav|qerax|kevî|perav|qirax|sahil}}
# [[kenar]]
#* {{jêder|ku|jêgirtin=di '''kersexa''' evina te de dil masîyek pirpitandî.|Rojen Barnas di helbesteke xwe de}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Jê ====
{{kol3|ku
|kersexistan
|kersexî
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W|af|kus}}, {{W|af|oewer}}
* {{Z|sq}}: {{W|sq|breg|xs=Albanian}}, {{W|sq|bregdet|xs=Albanian}}
* {{Z|de}}: {{W|de|Küste}} {{mê}}, {{W|de|Küstenland}}, {{W|de|Strand}} {{n}}, {{W|de|Ufer}} {{nt}}
* {{Z|bg}}: {{W-|bg|морски бряг|tr=morski brjat}}, {{W-|bg|крайбрежие|tr=krajbrežie}}
* {{Z|cs}}: {{W|cs|pobřeží}} {{nt}}, {{W|cs|břeh}} {{n}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W|cmn|海岸|tr=hǎi'àn}}, {{W|cmn|岸|tr=àn}}
* {{Z|da}}: {{W|da|bred}}, {{W|da|kyst}}
* {{Z|id}}: {{W|id|pantai|xs=Indonesian}}
* {{Z|ar}}: {{W|ar|ساحل}}, {{W|ar|شاطئ}}, {{W|ar|تلة}}, {{W|ar|هبوط تلة}}
* {{Z|hy}}: {{W|hy|ափ|tr=ap’}}
* {{Z|eo}}: {{W|eo|marbordo|xs=Esperanto}}
* {{Z|et}}: {{W|et|rand}}, {{W|et|rannik}}
* {{Z|fa}}: {{W|fa|ساحل|tr=sāhel}}, {{W|fa|دریاکنار|tr=daryā-kanār}}
* {{Z|fo}}: {{W|fo|strond|xs=Faroese}}
* {{Z|fi}}: {{W|fi|merenrannikko}}, {{W|fi|rannikko}}
* {{Z|fr}}: {{W|fr|côte}} {{mê}}
* {{Z|fy}}: {{W|fy|igge}}
* {{Z|gd}}: {{W|gd|cladach}} {{n}}
* {{Z|ka}}: {{W-|ka|ნაპირი|tr=napiri}}, {{W-|ka|ზღვის პირი|tr=zghvis piri}}
* {{Z|hi}}: {{W-|hi|सीमा|tr=sīmā}}, {{W-|hi|कनारा|tr=kanāra}}, {{W-|hi|तट|tr=tat.a}}, {{W-|hi|समुद्रतट|tr=samudra-tat.a}}, {{W-|hi|सागरतट|tr=sāgara-tat.a}}
* {{Z|nl}}: {{W|nl|kust}}, {{W|nl|kustlijn}}, {{W|nl|zeekant}}, {{W|nl|zeekust}}
* {{Z|he}}: {{W|he|שְׂפַת הַיָּם}}, {{W|he|חוֹף יָם}}, {{W|he|חוֹף}}
* {{Z|ang}}: {{W|ang|sæ-rima}}
* {{Z|en}}: {{W|en|coast}}, {{W|en|shore}}, {{W|en|beach}}
* {{Z|ga}}: {{W|ga|cósta|xs=Irish}}
* {{Z|it}}: {{W|it|costa}}, {{W|it|costiera}}, {{W|it|litorale}}
* {{Z|is}}: {{W|is|strönd}}
* {{Z|ja}}: [[海岸]] ([[かいがん]]) (kaigan)
* {{Z|ca}}: {{W|ca|costa}} {{mê}}
* {{Z|ko}}: {{W-|ko|연안|tr=yŏnan}}, {{W-|ko|해안|tr=hae'an}}
* {{Z|la}}: {{W|la|limbus}}, {{W|la|ora}}, {{W|la|orarius}}, {{W|la|acta}}, {{W|la|litus}}
* {{Z|lt}}: {{W|lt|krasts}}, {{W|lt|piekraste}}, {{W|lt|pajūris}}
* {{Z|hu}}: {{W|hu|part}}, {{W|hu|partvidék}}, {{W|hu|tengerpart}}
* {{Z|mn}}: {{W-|mn|тэнгисийн эрэг|tr=tėngisijn ėrėg}}, {{W-|mn|хаялга|tr=chajalga}}, {{W-|mn|далайн эрэг|tr=dalajn ėrėg}}, {{W-|mn|далайн хевее|tr=dalajn chevee}}
* {{Z|no}}: {{W|no|kyst}}
* {{Z|ug}}: {{W|ug|déngiz yaqisi}}
* {{Z|pap}}: {{W|pap|kanto}}, {{W|pap|kantu}}, {{W|pap|kosta}}
* {{Z|pl}}: {{W|pl|brzeg}}, {{W|pl|wybrzeże}}, {{W|pl|zjazd}}, {{W|pl|tor bobslejowy}}
* {{Z|pt}}: {{W|pt|costa}}, {{W|pt|beira}}
* {{Z|ro}}: {{W|ro|coastă}}, {{W|ro|țărm de mare}}, {{W|ro|coborâre la vale}}, {{W|ro|liman}}, {{W|ro|mal}}, {{W|ro|limită}}, {{W|ro|hotar}}
* {{Z|ru}}: {{W-|ru|морской берег|tr=mórskoj béreg}}, {{W-|ru|побережье|tr=poberéžje}}
* {{Z|sh}}: {{W|sh|obala}} {{mê}}
* {{Z|sh}}: {{W|sh|obala}}
* {{Z|sh}}:
*: Kirîlî: [[обала]] {{mê}}
*: Roman: [[obala#Serbian|obala]] {{mê}}
* {{Z|sk}}: {{W|sk|klesajúci}}, {{W|sk|morský breh}}, {{W|sk|nezaberajúci}}, {{W|sk|plávať pozdĺž pobrežia}}, {{W|sk|pobrežie}}, {{W|sk|pobrežie mora}}, {{W|sk|stúpajúci}}
* {{Z|es}}: {{W|es|costa}} {{mê}}, {{W|es|litoral}} {{n}}
* [[Sranan]]: [[syoro]], [[watrasey]], [[sekanti]]
* {{Z|sw}}: {{W|sw|pwani|xs=Swahili}}, {{W|sw|ufuko|xs=Swahili}}, {{W|sw|mapwa|xs=Swahili}}, {{W|sw|mapwaji|xs=Swahili}}
* {{Z|sv}}: {{W|sv|gräns mot hav}}, {{W|sv|kust}}, {{W|sv|strand}}
* {{Z|tl}}: {{W|tl|pampang}}, {{W|tl|baybayin}}
* {{Z|th}}: {{W-|th|ชายฝั่ง|tr=chaai-fàng}}, {{W-|th|ทางลาดเอียง|tr=taang lâat-iang}}
* {{Z|tr}}: {{W|tr|sahil}}, {{W|tr|deniz kenarı}}, {{W|tr|deniz kıyısı}}, {{W|tr|kıyı}}, {{W|tr|kızak için uygun yokuş}}, {{W|tr|kızakla yokuştan kayma}}
* {{Z|ur}}: {{W|ur|کنارہ|tr=kanāra}}, {{W|ur|تٹ|tr=tat.a}}, {{W|ur|ساگرتٹ|tr=sāgara-tat.a}}
* {{Z|uk}}: {{W-|uk|побережжя|tr=poberéžžja}}
* {{Z|vi}}: {{W|vi|bờ biển}}, {{W|vi|đường lao}}, {{W|vi|sự lao xuống}}, {{W|vi|sự lao dốc}}
* {{Z|cy}}: {{W|cy|arfordir}}, {{W|cy|goror}}, {{W|cy|mordir}}
* {{Z|yi}}: {{W|yi|breg|xs=Yiddish}}
* {{Z|el}}: {{W|el|ακρογιαλιά|tr=akrogialiá|m}}, {{W|el|γιαλός|tr=gialós|n}}, {{W|el|ακτή|tr=aktē|m}}, {{W|el|παραλία|tr=paralía|m}}
{{werger-bin}}
8f6mt52w5abb1fniefxswa3vqq156c7
6535025
6534989
2026-09-20T21:12:12Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir, --Valahiyên nehewce.)
6535025
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Squeaky Beach-Wilsons Prom-Vic.JPG|thumb|250px|kersexek]]
{{navdêr|ku|z=m}}
# [[cih|Cihê]] li [[ber]] [[av]]ê.
#: {{hevmane|ku|berav|qerax|kevî|perav|qirax|sahil}}
# [[kenar]]
#* {{jêder|ku|jêgirtin=di '''kersexa''' evina te de dil masîyek pirpitandî.|Rojen Barnas di helbesteke xwe de}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Jê ====
{{kol3|ku
|kersexistan
|kersexî
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W|af|kus}}, {{W|af|oewer}}
* {{Z|sq}}: {{W|sq|breg|xs=Albanian}}, {{W|sq|bregdet|xs=Albanian}}
* {{Z|de}}: {{W|de|Küste}} {{mê}}, {{W|de|Küstenland}}, {{W|de|Strand}} {{n}}, {{W|de|Ufer}} {{nt}}
* {{Z|bg}}: {{W-|bg|морски бряг|tr=morski brjat}}, {{W-|bg|крайбрежие|tr=krajbrežie}}
* {{Z|cs}}: {{W|cs|pobřeží}} {{nt}}, {{W|cs|břeh}} {{n}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W|cmn|海岸|tr=hǎi'àn}}, {{W|cmn|岸|tr=àn}}
* {{Z|da}}: {{W|da|bred}}, {{W|da|kyst}}
* {{Z|id}}: {{W|id|pantai|xs=Indonesian}}
* {{Z|ar}}: {{W|ar|ساحل}}, {{W|ar|شاطئ}}, {{W|ar|تلة}}, {{W|ar|هبوط تلة}}
* {{Z|hy}}: {{W|hy|ափ|tr=ap’}}
* {{Z|eo}}: {{W|eo|marbordo|xs=Esperanto}}
* {{Z|et}}: {{W|et|rand}}, {{W|et|rannik}}
* {{Z|fa}}: {{W|fa|ساحل|tr=sāhel}}, {{W|fa|دریاکنار|tr=daryā-kanār}}
* {{Z|fo}}: {{W|fo|strond|xs=Faroese}}
* {{Z|fi}}: {{W|fi|merenrannikko}}, {{W|fi|rannikko}}
* {{Z|fr}}: {{W|fr|côte}} {{mê}}
* {{Z|fy}}: {{W|fy|igge}}
* {{Z|gd}}: {{W|gd|cladach}} {{n}}
* {{Z|ka}}: {{W-|ka|ნაპირი|tr=napiri}}, {{W-|ka|ზღვის პირი|tr=zghvis piri}}
* {{Z|hi}}: {{W-|hi|सीमा|tr=sīmā}}, {{W-|hi|कनारा|tr=kanāra}}, {{W-|hi|तट|tr=tat.a}}, {{W-|hi|समुद्रतट|tr=samudra-tat.a}}, {{W-|hi|सागरतट|tr=sāgara-tat.a}}
* {{Z|nl}}: {{W|nl|kust}}, {{W|nl|kustlijn}}, {{W|nl|zeekant}}, {{W|nl|zeekust}}
* {{Z|he}}: {{W|he|שְׂפַת הַיָּם}}, {{W|he|חוֹף יָם}}, {{W|he|חוֹף}}
* {{Z|ang}}: {{W|ang|sæ-rima}}
* {{Z|en}}: {{W|en|coast}}, {{W|en|shore}}, {{W|en|beach}}
* {{Z|ga}}: {{W|ga|cósta|xs=Irish}}
* {{Z|it}}: {{W|it|costa}}, {{W|it|costiera}}, {{W|it|litorale}}
* {{Z|is}}: {{W|is|strönd}}
* {{Z|ja}}: [[海岸]] ([[かいがん]]) (kaigan)
* {{Z|ca}}: {{W|ca|costa}} {{mê}}
* {{Z|ko}}: {{W-|ko|연안|tr=yŏnan}}, {{W-|ko|해안|tr=hae'an}}
* {{Z|la}}: {{W|la|limbus}}, {{W|la|ora}}, {{W|la|orarius}}, {{W|la|acta}}, {{W|la|litus}}
* {{Z|lt}}: {{W|lt|krasts}}, {{W|lt|piekraste}}, {{W|lt|pajūris}}
* {{Z|hu}}: {{W|hu|part}}, {{W|hu|partvidék}}, {{W|hu|tengerpart}}
* {{Z|mn}}: {{W-|mn|тэнгисийн эрэг|tr=tėngisijn ėrėg}}, {{W-|mn|хаялга|tr=chajalga}}, {{W-|mn|далайн эрэг|tr=dalajn ėrėg}}, {{W-|mn|далайн хевее|tr=dalajn chevee}}
* {{Z|no}}: {{W|no|kyst}}
* {{Z|ug}}: {{W|ug|déngiz yaqisi}}
* {{Z|pap}}: {{W|pap|kanto}}, {{W|pap|kantu}}, {{W|pap|kosta}}
* {{Z|pl}}: {{W|pl|brzeg}}, {{W|pl|wybrzeże}}, {{W|pl|zjazd}}, {{W|pl|tor bobslejowy}}
* {{Z|pt}}: {{W|pt|costa}}, {{W|pt|beira}}
* {{Z|ro}}: {{W|ro|coastă}}, {{W|ro|țărm de mare}}, {{W|ro|coborâre la vale}}, {{W|ro|liman}}, {{W|ro|mal}}, {{W|ro|limită}}, {{W|ro|hotar}}
* {{Z|ru}}: {{W-|ru|морской берег|tr=mórskoj béreg}}, {{W-|ru|побережье|tr=poberéžje}}
* {{Z|sh}}: {{W|sh|obala}} {{mê}}
* {{Z|sh}}: {{W|sh|obala}}
* {{Z|sh}}:
*: Kirîlî: [[обала]] {{mê}}
*: Roman: [[obala#Serbian|obala]] {{mê}}
* {{Z|sk}}: {{W|sk|klesajúci}}, {{W|sk|morský breh}}, {{W|sk|nezaberajúci}}, {{W|sk|plávať pozdĺž pobrežia}}, {{W|sk|pobrežie}}, {{W|sk|pobrežie mora}}, {{W|sk|stúpajúci}}
* {{Z|es}}: {{W|es|costa}} {{mê}}, {{W|es|litoral}} {{n}}
* [[Sranan]]: [[syoro]], [[watrasey]], [[sekanti]]
* {{Z|sw}}: {{W|sw|pwani|xs=Swahili}}, {{W|sw|ufuko|xs=Swahili}}, {{W|sw|mapwa|xs=Swahili}}, {{W|sw|mapwaji|xs=Swahili}}
* {{Z|sv}}: {{W|sv|gräns mot hav}}, {{W|sv|kust}}, {{W|sv|strand}}
* {{Z|tl}}: {{W|tl|pampang}}, {{W|tl|baybayin}}
* {{Z|th}}: {{W-|th|ชายฝั่ง|tr=chaai-fàng}}, {{W-|th|ทางลาดเอียง|tr=taang lâat-iang}}
* {{Z|tr}}: {{W|tr|sahil}}, {{W|tr|deniz kenarı}}, {{W|tr|deniz kıyısı}}, {{W|tr|kıyı}}, {{W|tr|kızak için uygun yokuş}}, {{W|tr|kızakla yokuştan kayma}}
* {{Z|ur}}: {{W|ur|کنارہ|tr=kanāra}}, {{W|ur|تٹ|tr=tat.a}}, {{W|ur|ساگرتٹ|tr=sāgara-tat.a}}
* {{Z|uk}}: {{W-|uk|побережжя|tr=poberéžžja}}
* {{Z|vi}}: {{W|vi|bờ biển}}, {{W|vi|đường lao}}, {{W|vi|sự lao xuống}}, {{W|vi|sự lao dốc}}
* {{Z|cy}}: {{W|cy|arfordir}}, {{W|cy|goror}}, {{W|cy|mordir}}
* {{Z|yi}}: {{W|yi|breg|xs=Yiddish}}
* {{Z|el}}: {{W|el|ακρογιαλιά|tr=akrogialiá|m}}, {{W|el|γιαλός|tr=gialós|n}}, {{W|el|ακτή|tr=aktē|m}}, {{W|el|παραλία|tr=paralía|m}}
{{werger-bin}}
5nz5oc0gp6u1o3hacg29f23ugqiqnlf
gulat
0
62791
6534976
6307711
2026-09-20T18:59:58Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534976
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[girik|Girik]]ên [[ax]]ê.
#: {{hevmane|ku|şaltî|girik|kêsek|kêstek|gilok|gulolk|telte|kêrsek}}
#: {{nm|ku|gumtileke axê; gumtila şekirê/xwê}}
#* {{jêder|ku|jêgirtin=Hevalno, dema em axê dikolin û ax dibe girik girik, em ji wan parçeyên axê yên mezin re dibêjin ''şaltî''. Gelo hewe jî ev gotin bihîstiye?<br>Hogir Hingiv, ji devera Gabarê, bersivê dide:<br>- '''şaltî''' an jî '''gulat''' divê bi kulma destî bê girtin, lê heke jê mezintir be mirov nikare jê re bbêje şaltî an gulat|İdris Aksoy di koma "Zimanê Kurdî" ya li ser [[Rûname]]yê de, 1/2012}}
# {{kontrol|rêz=erê|[[gumtil]]}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|tr}}: {{W+|tr|yumru}}
{{werger-bin}}
06cnfq2p5eezv04lyvtyb58zb9agzml
6535006
6534976
2026-09-20T20:12:09Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6535006
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[girik|Girikên]] [[ax]]ê.
#: {{hevmane|ku|şaltî|girik|kêsek|kêstek|gilok|gulolk|telte|kêrsek}}
#: {{nm|ku|gumtileke axê; gumtila şekirê/xwê}}
#* {{jêder|ku|jêgirtin=Hevalno, dema em axê dikolin û ax dibe girik girik, em ji wan parçeyên axê yên mezin re dibêjin ''şaltî''. Gelo hewe jî ev gotin bihîstiye?<br>Hogir Hingiv, ji devera Gabarê, bersivê dide:<br>- '''şaltî''' an jî '''gulat''' divê bi kulma destî bê girtin, lê heke jê mezintir be mirov nikare jê re bbêje şaltî an gulat|İdris Aksoy di koma "Zimanê Kurdî" ya li ser [[Rûname]]yê de, 1/2012}}
# {{kontrol|rêz=erê|[[gumtil]]}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|tr}}: {{W+|tr|yumru}}
{{werger-bin}}
re71qkuc9ltndmhbe3u52oco5949b63
zelindar
0
63241
6534973
6385453
2026-09-20T18:58:57Z
Wikihez
7344
Kurmancî/Navdêr: beşa «Ji wêjeyê» hate rakirin (DefSideBoxes)
6534973
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Aristolochia sp.jpg|thumb|250px|zelindar]]
{{navdêr|ku|z=m}}
# [[badarok]], [[guhkitik]], [[halesek]], [[kulindok]], [[ritilsek]], [[gunhînk]]
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
====Navê zanistî====
* [[Aristolochia]]
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W-|de|Pfeifenblumen}}
* {{Z|az}}: {{W-|az|zəravənd}}
* {{Z|cs}}: {{W-|cs|podražec}}
* {{Z|zh}}: {{W-|zh|马兜铃属}}
* {{Z|da}}: {{W-|da|slangerod-slægten}}
* {{Z|ar}}: [[زراوند]] (zerawend)
* {{Z|eo}}: {{W-|eo|aristolokio}}
* {{Z|et}}: {{W-|et|tobiväät}}
* {{Z|fa}}: [[زراوند]] (zeravend)
* {{Z|fr}}: {{W+|fr|aristoloche}}
* {{Z|nl}}: {{W-|nl|aristolochia}}
* {{Z|he}}: {{W+|he|ספלול}}
* {{Z|io}}: {{W+|io|aristolokio}}
* {{Z|en}}: {{W-|en|aristolachia}}
* {{Z|ja}}: {{W-|ja|ウマノスズクサ属}}
* {{Z|ca}}: {{W-|ca|aristolòquia}}
* {{Z|ko}}: {{W-|ko|쥐방울덩굴속}}
* {{Z|lt}}: [[kartuolė (augalas)]]
* {{Z|ml}}: {{W+|ml|ആടുതൊടാപ്പാല}}
* {{Z|hu}}: {{W+|hu|farkasalma}}
* {{Z|pl}}: {{W-|pl|kokornak}}
* {{Z|pt}}: {{W-|pt|aristolochia}}
* {{Z|kk}}: {{W-|kk|Жиренше}}
* {{Z|ro}}: {{W-|ro|aristolochia}}
* {{Z|ru}}: {{W+|ru|кирказон}}
* {{Z|hsb}}: {{W-|hsb|kokornak}}
* {{Z|es}}: {{W-|es|aristolochia}}
* {{Z|sv}}: {{W-|sv|piprankesläktet}}
* {{Z|te}}: {{W-|te|అరిస్టోలోకియా}}
* {{Z|el}}: {{W-|el|Αριστολοχία}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=adeilnrz|lerizand}}
0kmxec9u8uojhq5wkplkmmt0tyjeg5i
6535007
6534973
2026-09-20T20:12:12Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Valahiya beşan rast kir, {{ku-tewîn-nav/nû}} → {{ku-tewîn-nav}}.)
6535007
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Aristolochia sp.jpg|thumb|250px|zelindar]]
{{navdêr|ku|z=m}}
# [[badarok]], [[guhkitik]], [[halesek]], [[kulindok]], [[ritilsek]], [[gunhînk]]
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Navê zanistî ====
* [[Aristolochia]]
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W-|de|Pfeifenblumen}}
* {{Z|az}}: {{W-|az|zəravənd}}
* {{Z|cs}}: {{W-|cs|podražec}}
* {{Z|zh}}: {{W-|zh|马兜铃属}}
* {{Z|da}}: {{W-|da|slangerod-slægten}}
* {{Z|ar}}: [[زراوند]] (zerawend)
* {{Z|eo}}: {{W-|eo|aristolokio}}
* {{Z|et}}: {{W-|et|tobiväät}}
* {{Z|fa}}: [[زراوند]] (zeravend)
* {{Z|fr}}: {{W+|fr|aristoloche}}
* {{Z|nl}}: {{W-|nl|aristolochia}}
* {{Z|he}}: {{W+|he|ספלול}}
* {{Z|io}}: {{W+|io|aristolokio}}
* {{Z|en}}: {{W-|en|aristolachia}}
* {{Z|ja}}: {{W-|ja|ウマノスズクサ属}}
* {{Z|ca}}: {{W-|ca|aristolòquia}}
* {{Z|ko}}: {{W-|ko|쥐방울덩굴속}}
* {{Z|lt}}: [[kartuolė (augalas)]]
* {{Z|ml}}: {{W+|ml|ആടുതൊടാപ്പാല}}
* {{Z|hu}}: {{W+|hu|farkasalma}}
* {{Z|pl}}: {{W-|pl|kokornak}}
* {{Z|pt}}: {{W-|pt|aristolochia}}
* {{Z|kk}}: {{W-|kk|Жиренше}}
* {{Z|ro}}: {{W-|ro|aristolochia}}
* {{Z|ru}}: {{W+|ru|кирказон}}
* {{Z|hsb}}: {{W-|hsb|kokornak}}
* {{Z|es}}: {{W-|es|aristolochia}}
* {{Z|sv}}: {{W-|sv|piprankesläktet}}
* {{Z|te}}: {{W-|te|అరిస్టోలోకియా}}
* {{Z|el}}: {{W-|el|Αριστολοχία}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=adeilnrz|lerizand}}
1c5hjllf1qmpbdeb40y0fgrn5x8p3sg
cirîd
0
178664
6534974
6339927
2026-09-20T18:59:27Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534974
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Two_men_playing_cirid.jpg|thumb|250px|du kes cirîdê dikin]]
{{navdêr|ku|z=m}}
# [[lîstik|Lîstikek]] anku [[yarî|yariyeke]] bi [[hesp]]an û [[rim]]an e.
#* {{jêder|ku|jêgirtin=Li Qersê rikberiya '''cirîda''' hespan|Rudaw.net/kurmanci, 1/2017}}
==== Formên alternatîv ====
{{kol3|ku
|cerîd
}}
==== Jê ====
{{kol3|ku
|cirîdbaz
|cirîdbazî
|cirîdî
|cirîdvan
|cirîdvanî
}}
==== Etîmolojî ====
Ji {{ji|ar|ku|جريد|tr=cerîd}}
==== Werger ====
{{werger-ser}}
* {{Z|en}}: {{W+|en|jereed}}, {{W+|en|jerid}}, {{W+|en|javelin}}
* {{Z|tr}}: {{W+|tr|cirit}}
{{werger-bin}}
41a1pxwltv7urxkdwrsesagimn91xg8
booted eagle
0
199855
6534898
6033323
2026-09-20T13:59:35Z
Keyomerd
8814
/* Navdêr */
6534898
wikitext
text/x-wiki
== {{ziman|en}} ==
=== Navdêr ===
[[Wêne:Booted eagle (India, 2012).jpg|thumb|a '''booted eagle''']]
{{en-navdêr}}
# {{f|en|balinde}} Balindeya kin, ''[[Hieraetus]]''.
==== Binêre herwiha ====
* {{wpedî|ziman=en}}
j31ogzws9pp02vrdrixqpi3hxaifmzx
6534911
6534898
2026-09-20T14:41:09Z
Balyozbot
7867
[[Special:PermanentLink/6298023|Bot]]: rêzkirina beşan
6534911
wikitext
text/x-wiki
== {{ziman|en}} ==
=== Navdêr ===
[[Wêne:Booted eagle (India, 2012).jpg|thumb|a '''booted eagle''']]
{{en-navdêr}}
# {{f|en|balinde}} Balindeya kin, ''[[Hieraetus]]''.
==== Herwiha binêre ====
* {{wpedî|ziman=en}}
e7k5fjsxw4gb8zwcwg5qhl25nlc60tu
daisy
0
200169
6535053
6088205
2026-09-21T09:14:04Z
Kurdistanov
15290
6535053
wikitext
text/x-wiki
== {{ziman|en}} ==
=== Bilêvkirin ===
* {{deng|en|en-us-daisy.ogg|dever=DYA}}
=== Navdêr ===
{{navdêr|en}}
# [[beybûn]], kulîlka biharê, (''[[botanîk]]'') bellis perennis, (argo) tişta pir xweşik
=== Jê ===
* [[ox-eye daisy]]
p2yzcgvrj3czyvu3w3h7f2jw11vivsk
Trüffel
0
249170
6535061
4122758
2026-09-21T09:40:05Z
Kurdistanov
15290
/* Navdêr */
6535061
wikitext
text/x-wiki
== {{ziman|de}} ==
=== Bilêvkirin ===
* {{deng|de|De-Trüffel.ogg|Deng}}
=== Navdêr ===
{{navdêr|de|z=nt}}
# [[dobelan]]
ae4x4k72jwnqff0qvayy62qzlpxaghr
6535062
6535061
2026-09-21T09:40:24Z
Kurdistanov
15290
/* Navdêr */
6535062
wikitext
text/x-wiki
== {{ziman|de}} ==
=== Bilêvkirin ===
* {{deng|de|De-Trüffel.ogg|Deng}}
=== Navdêr ===
{{navdêr|de|z=m}}
# [[dobelan]]
qrpmsyrhn1pqz3u3jz7xbkdtao3t44d
6535065
6535062
2026-09-21T10:29:25Z
Balyozbot
7867
[[Bikarhêner:Wikihez/skrîpt/py/dengbot.py|Bot]]: Formata parametreya "dever" hat guhartin
6535065
wikitext
text/x-wiki
== {{ziman|de}} ==
=== Bilêvkirin ===
* {{deng|de|De-Trüffel.ogg}}
=== Navdêr ===
{{navdêr|de|z=m}}
# [[dobelan]]
a889gv20j95jgf1548969fcegb3icls
dewletok
0
252445
6534971
6305168
2026-09-20T18:52:01Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534971
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[mîkrodewlet]], [[mînîdewlet]], [[dewlet]]ên [[gelek]] [[biçûk]]
#* {{jêder|ku|jêgirtin=Rêberê PKK-ê Abdullah Ocalan bi daxuyaniyeka nivîskî ko îro li newroza Diyarbekirê hat xwendin got dewletên emperyalîst van dused salên dawiyê civaka ereb, faris, tirk û kurdan di ”'''dewletokên''' neteweyî” de fetisandine û got avakirina cografyayên yekneteweyî jî îcadeka neinsanî ye.|Nefel.com, 3/2012}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Jê ====
{{kol3|ku
|dewletokî
}}
==== Etîmolojî ====
Ji {{paş|ku|dewlet|-ok}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Zwergstaat}}
* {{Z|cs}}: {{W+|cs|ministát}}
* {{Z|zh}}: {{W+|zh|微型国家}}
* {{Z|da}}: {{W+|da|mikrostat}}
* {{Z|ar}}: {{W+|ar|دولة صغيرة}}
* {{Z|eo}}: {{W+|eo|mikroŝtato}}
* {{Z|fi}}: {{W+|fi|kääpiövaltio}}
* {{Z|fr}}: {{W+|fr|micro-état}}
* {{Z|nl}}: {{W+|nl|dwergstaat}}
* {{Z|en}}: {{W+|en|microstate}}, {{W-|en|statelet}}, {{W|en|petty state|guman=erê}}
* {{Z|it}}: {{W+|it|microstato}}
* {{Z|ja}}: {{W+|ja|ミニ国家}}
* {{Z|ca}}: {{W+|ca|microestat}}
* {{Z|ko}}: {{W+|ko|소국}}
* {{Z|li}}: {{W+|li|dwergsjtaot}}
* {{Z|lt}}: {{W+|lt|mikrovalstybė}}
* {{Z|lb}}: {{W+|lb|Zwergstaat}}
* {{Z|mt}}: {{W+|mt|mikrostat}}
* {{Z|hu}}: {{W+|hu|törpeállam}}
* {{Z|no}}: {{W+|no|mikrostat}}
* {{Z|pt}}: {{W+|pt|microestado}}
* {{Z|ro}}: {{W+|ro|microstat}}
* {{Z|ru}}: {{W+|ru|карликовое государство}}
* {{Z|sk}}: {{W+|sk|miništát}}
* {{Z|es}}: {{W+|es|microestado}}
* {{Z|sv}}: {{W+|sv|mikrostat}}
* {{Z|tr}}: {{W+|tr|mikrodevlet}}
* {{Z|uk}}: {{W+|uk|мікродержава}}
* {{Z|cy}}: {{W+|cy|microwladwriaeth}}
{{werger-bin}}
8puj9vdx2sndhop4bqrn1fuekini326
xerçeng
0
266436
6534969
6339769
2026-09-20T18:50:15Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534969
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=?}}
# [[kêvjal]], [[kevjal]], [[penceşêr]], [[kêmjale]], [[kêjmale]], [[kêşmale]]
# soreta felekî a seretan di esmên de
#* {{J:Melayê Cizîrî:Dîwana Melayê Cizîrî|jêgirtin=Newaya mutribê çengê<br>fixan avête '''xerçengê'''<br>Were saqî heta kengê<br>neşoyîn dil ji vê jengê}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|ar}}: {{W-|ar|سلطعون}}, {{W-|ar|سرطان}}
* {{Z|fa}}: {{W+|fa|خرچنگ}}
* {{Z|en}}: {{W+|en|crab}}
* {{Z|tr}}: {{W+|tr|yengeç}}, {{W-|tr|yengeç burcu}}, {{W+|tr|Yengeç|guman=erê}}
{{werger-bin}}
te3qvlx2vrgb623d54uvpubgfb2xha8
کەو
0
272468
6534908
6237970
2026-09-20T14:28:06Z
Keyomerd
8814
/* Navdêr */
6534908
wikitext
text/x-wiki
== {{ziman|ckb}} ==
=== Bilêvkirin ===
* {{ckb-IPA}}
=== Navdêr ===
{{navdêr|ckb}}
# {{f|ckb|teyr}} [[kew]], firrindeyê neteweyî yê kurdan
# [[kew]], saxkirina birînan
# [[moxel]], [[bêjing]], alavek e bo bijartina t birinc yan ar ji qirşikên pîs
r8zuver92qtenrj118sv6p141sivadr
6534909
6534908
2026-09-20T14:31:06Z
Wikihez
7344
Soranî/Navdêr: mane hate guhartin, mane hate guhartin, mane hate guhartin (DefSideBoxes)
6534909
wikitext
text/x-wiki
== {{ziman|ckb}} ==
=== Bilêvkirin ===
* {{ckb-IPA}}
=== Navdêr ===
{{navdêr|ckb}}
# {{f|ckb|teyr}} [[kew]] {{îzah|[[firrinde|firrindeyê]] [[neteweyî|neteweyî]] yê [[kurd|kurdan]]}}
# [[kew]] {{îzah|[[saxkirin|saxkirina]] [[birîn|birînan]]}}
# [[moxel]], [[bêjing]] {{îzah|[[alav|alavek]] e bo [[bijartin|bijartina]] [[birinc|birinc]] yan ar ji [[qirşik|qirşikên]] [[pîs|pîs]]}}
kisv78nvvj2cw2jf9fjgcx9j215v4kh
6534916
6534909
2026-09-20T16:12:07Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6534916
wikitext
text/x-wiki
== {{ziman|ckb}} ==
=== Bilêvkirin ===
* {{ckb-IPA}}
=== Navdêr ===
{{navdêr|ckb}}
# {{f|ckb|teyr}} [[kew]] {{îzah|[[firrinde]]yê [[neteweyî]] yê [[kurd]]an}}
# [[kew]] {{îzah|[[saxkirin]]a [[birîn]]an}}
# [[moxel]], [[bêjing]] {{îzah|[[alav]]ek e bo [[bijartin]]a [[birinc]] yan ar ji [[qirşik]]ên [[pîs]]}}
fp09xp1g2iqxtai6em6k76aep2ihwu2
qulûb
0
279297
6534917
6141527
2026-09-20T16:42:41Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534917
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku}}
# [[pirjimar]] ji '''[[qelb]]'''
#* {{J:Mem û Zîn:2022|jêgirtin=Mehbûbê '''qulûbê''' "men lehu'l-qelb"<br>Qelban tu dikî bi ba xwe ve celb}}
==== Etîmolojî ====
Ji {{deyn|ar|ku}} ye.
0c41fpafi5stl8f9m5njquo1a29dlzb
mukaşefat
0
279319
6534921
6284844
2026-09-20T16:47:35Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534921
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku}}
# Sirrên ku bi rêya Xwedê bi îlhamekê digehe mutesewwifan.
#* {{J:Mem û Zîn:1692|jêgirtin=namê te ye şahibeytê meqsûd<br>fihristê mukatebatê mehmûd<br>medmûnê muraselatê la-reyb<br>meşhûdê '''mukaşefatê''' bi-l-xeyb<br>mehbûbê qulûbê men lehu l-qelb<br>qelban tu dikî bi ba xwe ve celb<br>me’şûq tu yî, bi fexr û naz î<br>‘aşiq tu yî, lêk bêniyaz î<br>Mutleq tu mufîd û mustefad î<br>Bêşubhe murîd û hem murad î<br>Nûr î tu di husn-i rûyê dildar<br>Nar î tu di qelbê ‘aşiqê zar}}
==== Têkildar ====
{{kol3|ku
|keşf,mukaşefe,tesewwif,mutesewwif
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
{{werger-bin}}
mmarwfgfbgtm2po8ehjuer2qvfbpwqv
سامان
0
293900
6534920
6527546
2026-09-20T16:46:24Z
Balyozbot
7867
sererastkirina nimûneyên edebiyatê (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6534920
wikitext
text/x-wiki
== {{ziman|fa}} ==
=== Bilêvkirin ===
:saman
=== Navdêr ===
{{navdêr|fa|tr=}}
# [[sermiyan]], [[semyan]], [[milk]], [[mal]], [[maliyet]], [[mewdan]], [[kapîtal]] {{îzah|tiştên kesekê/î hene (bi taybetî yên dikarin bên [[kirîn]] û [[firotin]])}}
# [[pergal]], [[sazûman]], [[nîzam]]
#* {{jêder|fa|jêgirtin=دارم از زلف سیاهش گله چندان که مپرس<br>که چنان ز او شدهام بی سر و '''سامان''' که مپرس<br>کس به امید وفا ترک دل و دین مکناد<br>که چنانم من از این کرده پشیمان که مپرس|[[w:Hafizê Şîrazî]]}}
==== Jê ====
* [[سامان دادن]]
* [[سامان یافتن]]
==== Etîmolojî ====
Ji {{ji|pal|fa}}
==== Anagram ====
* {{anagram|fa|a=ااسمن|اسمان}}
== {{ziman|ug}} ==
=== Navdêr ===
{{navdêr|ug}}
# {{g|ku|ka}}, {{g|ku|pûş}} (giyayê hişkkirî bo ku sewal bixwin)
=== Çavkanî ===
{{rûpela wergerê|ug|ka}}
== {{ziman|ps}} ==
=== Navdêr ===
{{navdêr|ps|tr=sāmãn|z=n}}
# {{mijar|ps|amûr}} {{g|ku|amûr}}, {{g|ku|alet}}
=== Çavkanî ===
{{rûpela wergerê|ps|amûr}}
== {{ziman|ur}} ==
=== Navdêr ===
{{navdêr|ur|tr=sāmān|z=n}}
# {{g|ku|kelûpel}}, {{g|ku|eşya}}, {{g|ku|tişt}}
=== Çavkanî ===
{{rûpela wergerê|ur|kelûpel}}
o47swsyydw4bvaet0udx5sczad1ds77
şolgenîk
0
313797
6535064
6323144
2026-09-21T09:56:13Z
Kurdistanov
15290
6535064
wikitext
text/x-wiki
== {{ziman|ku}} ==
[[Wêne:Vicia sativa.jpg|thumb]]
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# {{f|ku|Xorasan|riwek}} Giyayek ku li deşta û çiyayan çêdibe.
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Formên alternatîv ====
* [[baqilê gamêşan]]
* [[kuşne]]
* [[baqilfisek]]
==== Werger ====
{{werger-ser}}
* {{Z|fa}}: {{W+|fa|ماشک}}
* {{Z|en}}: {{W-|en|common vetch}}
* {{Z|tr}}: {{W-|tr|arpa fiği}}, {{W-|tr|buğday fiği}}, {{W-|tr|bir tür burçak}}, {{W-|tr|yabani burçak}}
{{werger-bin}}
=== Çavkanî ===
* https://web.archive.org/web/20140303015131/http://zanin.ir/fehrista-giyahan.html
8y9o5hyux3ze0vd3edn347pehxlz7lz
6535071
6535064
2026-09-21T11:12:44Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk ({{ku-tewîn-nav/nû}} → {{ku-tewîn-nav}}.)
6535071
wikitext
text/x-wiki
== {{ziman|ku}} ==
[[Wêne:Vicia sativa.jpg|thumb]]
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# {{f|ku|Xorasan|riwek}} Giyayek ku li deşta û çiyayan çêdibe.
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
* [[baqilê gamêşan]]
* [[kuşne]]
* [[baqilfisek]]
==== Werger ====
{{werger-ser}}
* {{Z|fa}}: {{W+|fa|ماشک}}
* {{Z|en}}: {{W-|en|common vetch}}
* {{Z|tr}}: {{W-|tr|arpa fiği}}, {{W-|tr|buğday fiği}}, {{W-|tr|bir tür burçak}}, {{W-|tr|yabani burçak}}
{{werger-bin}}
=== Çavkanî ===
* https://web.archive.org/web/20140303015131/http://zanin.ir/fehrista-giyahan.html
mw6sbbl1vbf29jrqrf0zbk4i7vrdbwu
dobelan
0
316791
6535060
6394242
2026-09-21T09:39:29Z
Kurdistanov
15290
/* Werger */
6535060
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Keme.jpg|thumb|Dobelana ji famîleya ''[[Terfeziaceae]]'']]
{{navdêr|ku|z=m}}
# Cureyek [[kuvark]]an e. Ji qismê "Ascomycete" û ji [[famîlye]]ya "Tuberaceae" ye. Li bin erdê mezin dibe. Dişibe [[kartol]]an. Hinek cinsen wan pir biha ne. Li Bakurê Kurdistanê li gelek deran, li [[çol]] û [[beyar]]an bi xwe çêdibe
#* {{J:Sadiq Ûskan:Şampaz|jêgirtin=De îcar çend rojan ro bide erdê û tu ji xwe ra li '''dobelanan''' binihêrî.}}
==== Formên alternatîv ====
* [[dumbelan]]
* [[dûmelan]]
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Trüffel|m}}
* {{Z|fa}}: {{W+|fa|دنبلان}}
* {{Z|fr}}: {{W+|fr|tuber}}
* {{Z|en}}: {{W+|en|truffle}}
* {{Z|tr}}: {{W+|tr|keme mantarı}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=abdelno|bodlane}}
8vkatk1m6q3mqsm97g7y5iarvqrwcdd
bedewnivîs
0
344213
6534984
6298425
2026-09-20T19:20:33Z
Wikihez
7344
[[Bikarhêner:Wikihez/skrîpt/js/şablon-lêke.js|T]]: +{{bêçavkanî}}
6534984
wikitext
text/x-wiki
{{bêçavkanî}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[xetat]], [[nexşnivîs]], [[spehînivîs]], [[kalîgraf]], [[kalîgrafer]]
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Etîmolojî ====
Ji {{pêk|ku|bedew|nivîs}}.
==== Werger ====
{{werger-ser}}
* {{Z|tr}}: {{W+|tr|hattat|guman=erê}}
{{werger-bin}}
kaafrx6isusqdurhkfpip7j9e8pykb9
6535027
6534984
2026-09-20T21:12:18Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk ({{ku-tewîn-nav/nû}} → {{ku-tewîn-nav}}.)
6535027
wikitext
text/x-wiki
{{bêçavkanî}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[xetat]], [[nexşnivîs]], [[spehînivîs]], [[kalîgraf]], [[kalîgrafer]]
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
Ji {{pêk|ku|bedew|nivîs}}.
==== Werger ====
{{werger-ser}}
* {{Z|tr}}: {{W+|tr|hattat|guman=erê}}
{{werger-bin}}
ogqo1cr2nmlkpdht6ej0eudr8gjslrp
spehînivîs
0
344214
6534987
6318021
2026-09-20T19:21:14Z
Wikihez
7344
[[Bikarhêner:Wikihez/skrîpt/js/şablon-lêke.js|T]]: +{{bêçavkanî}}
6534987
wikitext
text/x-wiki
{{bêçavkanî}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[xetat]], [[nexşnivîs]], [[bedewnivîs]], [[kalîgraf]], [[kalîgrafer]]
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
gab9a094j7ldg2nr31gej75iygn9lug
6535026
6534987
2026-09-20T21:12:15Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk ({{ku-tewîn-nav/nû}} → {{ku-tewîn-nav}}.)
6535026
wikitext
text/x-wiki
{{bêçavkanî}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[xetat]], [[nexşnivîs]], [[bedewnivîs]], [[kalîgraf]], [[kalîgrafer]]
==== Tewandin ====
{{ku-tewîn-nav|mê}}
5dc1ctqgiuxjh3kykbqtrukxzr5filn
devtî
0
377341
6534922
6388169
2026-09-20T17:40:05Z
Wikihez
7344
/* Kurmancî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6534922
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[nîşa]], [[nîşast]]
==== Formên alternatîv ====
* [[devtu]]
* [[devtû]] {{mj|Reşwendî}}
==== Etîmolojî ====
Ji {{qertaf|ku|dev|-tî}}.
==== Werger ====
{{werger-ser}}
* {{Z|tr}}: {{W+|tr|nişasta}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=deîtv|evdtî}}
i755i4qvvcaejiemyagce057mfeu1oq
rûberkirin
0
429714
6535066
6316944
2026-09-21T10:55:46Z
Bikarhêner
3719
/* Navdêr */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6535066
wikitext
text/x-wiki
{{bnr-l|rûber kirin}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# rastandin
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Etîmolojî ====
Ji {{pêk|ku|rûber|kirin}}.
==== Werger ====
{{werger-ser}}
* {{Z|en}}: {{W+|en|endorsement}}
{{werger-bin}}
219y3c0l3m2xqpw699cygjpyi6rx75t
darbahîv
0
653848
6535049
6246882
2026-09-21T09:02:27Z
Kurdistanov
15290
/* Navdêr */
6535049
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Almond tree in blossom.jpg|thumb|darbahîv]]
{{navdêr|ku|z=m}}
# [[dar]]a ku [[bahîv]] [[pê ve]] tên, dara [[badam]]ê
==== Bi alfabeyên din ====
* {{ku-ar|دارباهیڤ}}
==== Formên alternatîv ====
* [[darbehîv]]
* [[dara bahîvê]]
==== Hevmane ====
* [[bahîv]]
==== Etîmolojî ====
Ji {{qertaf|ku|dar|bahîv}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Mandelbaum}}
* {{Z|ar}}: {{W-|ar|لوز}}
* {{Z|fa}}: {{W-|fa|باوی}}
* {{Z|en}}: (tree) {{W+|en|almond}}
* {{Z|tr}}: {{W+|tr|badem}}
{{werger-bin}}
kioy8rjamwpij5jzx2o7ztopbn1bwgs
6535050
6535049
2026-09-21T09:09:39Z
Kurdistanov
15290
6535050
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Almond tree in blossom.jpg|thumb|darbahîv]]
{{navdêr|ku|z=m}}
# [[dar]]a ku [[bahîv]] [[pê ve]] tên, dara [[badam]]ê
==== Bi alfabeyên din ====
* {{ku-ar|دارباهیڤ}}
==== Formên alternatîv ====
* [[darbehîv]]
* [[dara bahîvê]]
==== Hevate ====
* [[bahîv]]
* [[çaqale]]
* [[behîva bejî]]
* [[badema çiya]]
==== Etîmolojî ====
Ji {{qertaf|ku|dar|bahîv}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Mandelbaum}}
* {{Z|ar}}: {{W-|ar|لوز}}
* {{Z|fa}}: {{W-|fa|باوی}}
* {{Z|en}}: (tree) {{W+|en|almond}}
* {{Z|tr}}: {{W+|tr|badem}}
{{werger-bin}}
hfraqvmu08n5jagdwyekli13hhm32fq
6535063
6535050
2026-09-21T09:41:07Z
Balyozbot
7867
[[Special:PermanentLink/6298023|Bot]]: rêzkirina beşan
6535063
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Almond tree in blossom.jpg|thumb|darbahîv]]
{{navdêr|ku|z=m}}
# [[dar]]a ku [[bahîv]] [[pê ve]] tên, dara [[badam]]ê
==== Bi alfabeyên din ====
* {{ku-ar|دارباهیڤ}}
==== Formên alternatîv ====
* [[darbehîv]]
* [[dara bahîvê]]
==== Etîmolojî ====
Ji {{qertaf|ku|dar|bahîv}}.
==== Hevate ====
* [[bahîv]]
* [[çaqale]]
* [[behîva bejî]]
* [[badema çiya]]
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Mandelbaum}}
* {{Z|ar}}: {{W-|ar|لوز}}
* {{Z|fa}}: {{W-|fa|باوی}}
* {{Z|en}}: (tree) {{W+|en|almond}}
* {{Z|tr}}: {{W+|tr|badem}}
{{werger-bin}}
e205n53shl9hi0ydt32x82p8vyqo5c7
Wîkîferheng:Daxwazên peyitandinê
4
843283
6534985
6532787
2026-09-20T19:20:32Z
Wikihez
7344
[[Bikarhêner:Wikihez/skrîpt/js/şablon-lêke.js|T]]: Daxwaza peyitandinê: +[[bedewnivîs]]
6534985
wikitext
text/x-wiki
{{/destpêk}}
= Hezîran 2020 =
== [[romî#Navdêr|romî]] ==
Li Şingalê maneya polîs? --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:00, 15 hezîran 2020 (UTC)
= Tîrmeh 2020 =
== [[kêlb#p-mane|kêlb]] ==
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:24, 22 tîrmeh 2020 (UTC)
== [[ejî#p-mane|ejî]] ==
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:24, 22 tîrmeh 2020 (UTC)
== [[tutirk#peyitandin|tutirk]] ==
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:02, 22 tîrmeh 2020 (UTC)
:{{r|Guherto}} Niha çawa ye? Bi ya min çavkaniyên ku min dane têrê me dike. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:43, 22 tîrmeh 2020 (UTC)
::{{r|Balyozxane}} Nizanim.. li hin deveran wek tutirk hatîye nivîsandin û li hin jî wek tûtirk. Li hin deveran bi wateya tûşêmî (Rubus) ye, hin deveran jî bi wateya tûerd (Fragaria × ananassa) e û carna jî bi wateya tû (ê spî û reş go li ser daran çê dibin) ye. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 08:16, 23 tîrmeh 2020 (UTC)
== [[erbane#p-mane|erbane]] ==
Ji bo ''Amûreke tenê tekerekî wê heye ji bo veguhestina tiştan wek: axê, kevir, hwd'' ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]''--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:42, 31 tîrmeh 2020 (UTC)
= Tebax 2020 =
== [[cifta berazan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:57, 9 tebax 2020 (UTC)
== [[tê gerîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:26, 10 tebax 2020 (UTC)
== [[tapor]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:30, 22 tebax 2020 (UTC)
:{{r|Mihemed Qers}} -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 22:16, 22 tebax 2020 (UTC)
== [[şimzare]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:01, 26 tebax 2020 (UTC)
== [[bîrkok]] ==
Tenê Mehmed Uzun bi kar tîne. Ji bilî kitêbên wî çavkaniyeke din tune. —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:38, 28 tebax 2020 (UTC)
== [[hunera axaftinê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:46, 30 tebax 2020 (UTC)
== [[derrijîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 07:38, 31 tebax 2020 (UTC)
== [[yekal]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 09:19, 31 tebax 2020 (UTC)
== [[av û agir]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:50, 31 tebax 2020 (UTC)
: Di çavkaniyeke de wek av û agir kirin derbas dibe û ji wergerandinên inglîzî cuda ye lê nêzîk e.<br> {{jêder-pirtûk|ku|jêgirtin=weke gelek hunermendên ko dû re agir bera berhêm xwe didin Xwedê jî ji bilî maltaba Nûh tevayiyê vê berhema xwe '''av û agir dike''', van melekên xwe yên doxînsist dikişîne asîmanan, li wir wan tawanbar dike, mîna ko bibêje: de biçin û weke xwe bikin, bi derew û tevgerên xwe yên qirêj dijminiya min bikin, Xwedê wan ji asimanan de serjêr davêje xwarê.|nivîskar=Hesenê Metê|sernav=Labîrenta cinan|weşanxane=Nefel|sal=2002|rûpel=132}} --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:37, 31 tebax 2020 (UTC)
== [[elm#p-mane-notice|elm]] ==
Wateya: ''[[ewlebûn]], sihê pê zanîn, baş pê zanîn''. ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:31, 31 tebax 2020 (UTC)
:{{r|Hakîmo}} Ku çavkanî tunebûn em ê jê bibin. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:47, 19 îlon 2020 (UTC)
::{{r|Balyozxane}} Mala te neketê... Hingê bixwe here lê bigere. Jixwe ez reşwî me. Ma eve ne çavkanî ye? -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 14:48, 19 îlon 2020 (UTC)
:::{{r|Hakîmo}} Hinek bixwîne [[Wîkîferheng:Pîvekên tevlîkirinê]]. Jixwe dema ti kesan din çavkanî lê zêde nakin, ez lê digerim û zêde dikim. Na tu ne ti çavkanî yî. Çavkaniyên nivîskî hewce ne. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:51, 19 îlon 2020 (UTC)
::::{{r|Balyozxane}} Erê, lê ku tine bin em çawa bikinî? Em hema jê bibinî? -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 14:52, 19 îlon 2020 (UTC)
:::::{{r|Hakîmo}} Erê em jê dibin. Lê peyvên jê hatine birin em li rûpela [[Pêvek:Bêçavkanî]] zêde dikin. bnr. [[:Kategorî:Pêvek bêçavkanî]]. Dîsa jî ez jî ji vê rewşa me aciz im û bi ya min peyvên devokî divê nayên jêbirin. Lê ji ber ku [[Wîkîferheng:Rêbazname]] dibêje divê werin jêbirin, ez jê dibim. Hevalên {{r|Jiju|Guherto|Ghybu}} [https://ku.wiktionary.org/wiki/Got%C3%BBb%C3%AAj:kejm%C3%BBn], [https://ku.wiktionary.org/wiki/Got%C3%BBb%C3%AAja_p%C3%AAvek%C3%AA:B%C3%AA%C3%A7avkan%C3%AE] jî ji vê rewşê aciz in. Lê ti kes gotûbêj nadin destpêkirin. Ku tu li vir [[Gotûbêja Wîkîferhengê:Pîvekên tevlîkirinê]] gotûbêjek bidî destpêkirin, ez ê piştgirî bidime te. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:58, 19 îlon 2020 (UTC)
::::::Ji kerema xwe min şaş fêm mekin feqet hemû bikarhêner ku tên vira dilpak nînin. Em çito ew peyvan kotrol bikinê? Mixabin, ji bo konrolkirina peyvan di destên me de tenê xwastina çavkaniyan hene amûrek din tine ye... PS: Herwiha me li ser wîkîferhengê Namespace'yên Nimînok ([[Nimînok:Kurdî/serhedî]]?, [[Nimînok:Kurdî/badînî]]?, ..) û Portal ([[Portal:Kurdî]], [[Portal:Frensî]] (mk. [[:fr:Portail:Français]]), ...) jî çêkiriye...--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:32, 19 îlon 2020 (UTC)
:::::::Nizanim we nivîsa vira xwand, [[Wîkîferheng:Dîwan#Skotî]]. Piraniya peyvên wîkiya skotî çêkirî ne...--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:47, 19 îlon 2020 (UTC)
::::::::@Ghybu erê min ew xwendibû. Ku em peyvan nepeyitînin, rewşa me jî wek mg.wikt û wikiya skotiyan be. Temam em peyvên devokî jê nebin lê em wan jî nexin werger û rûpelên din heta ku werin peyitandin. Herwiha tu dibêjî amûrek tune ye lê tenê ji bo peyvên devokî û navên heywan/riwekan wisa ye. Gelek caran peyitandin pir hêsan e. Mînak @Hakîmo yan jî hevalên din dikarin blogan binivîsin daku em karibin peyvên wan yên devokî (nepeyitandî) têxin rûpela [[Wîkîferheng:Tomarkirina peyvên ji wêjeya kurdî|Jêgirtinekê tomar bike]] û kengî ew peyv ji hêla nivîskar û blogvanên kurd yên din ve hatine bikaranîn hingê em dikarin li wîkîferhengê jî zêde bikin. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:05, 19 îlon 2020 (UTC)
{{outdent|::::::::}}{{r|Hakîmo|Ghybu}} Xêra dê û bavên we '''[[Gotûbêja_Wîkîferhengê:Pîvekên_tevlîkirinê#Li_ser_peyvên_devokî|li vir]]''' fikrên xwe binivîsin daku kesên din bizanin kesên din jî hene vê fikrê min dipejirînin. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 06:48, 3 çiriya pêşîn 2020 (UTC)
== [[nîşanbar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:51, 31 tebax 2020 (UTC)
= Îlon 2020 =
== [[besme]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:26, 1 îlon 2020 (UTC)
== [[ferengi]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:12, 1 îlon 2020 (UTC)
== [[çelape]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:22, 1 îlon 2020 (UTC)
== [[cesasandin]] ==
Çavkaniyek klasîk heye, yên din heye gelo? —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 09:02, 2 îlon 2020 (UTC)
== [[depan]] ==
Depan guhertoyeke textan an textîn e. Lê li vir çi mane heye min fêm nekir. —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:33, 2 îlon 2020 (UTC)
== [[li rûyê dinê xweş mayîn]] ==
Çavkaniyeke klasîk heye —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:38, 2 îlon 2020 (UTC)
== [[bêsewî]] ==
klasîk —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 06:34, 4 îlon 2020 (UTC)
== [[merkemoş]] ==
Klasîk —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 06:34, 4 îlon 2020 (UTC)
== [[firfiroka bayî]] ==
{{r|Mamoste Dilovan}} ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:57, 6 îlon 2020 (UTC)
== [[guhê fîlî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:56, 7 îlon 2020 (UTC)
: {{r|Bikarhêner}} Min guh li evê peyvê bû ji kesekî ku xudan xwaringehek û baxçeyekî mezin e.
: Gelek tişt di berbelav in û xelk dizanin, lê di pertûkan de naye dîtin. {{bêîmze|IbrahimKocher Duhok}}
== [[gurovî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:41, 14 îlon 2020 (UTC)
:{{r|Bikarhêner}} Çavkanîyên peyvên ku min nivîsandin mixabin ne nivîskî ne. Ev peyv min ji hin mezinên malbatê bihîstine û min jî gelek caran dîtîne ku hatine bikaranîn. Ji bo ku ev peyv wûnda nebin min hem li ba xwe û hem jî li wîkîferhengê nivîsand. Ji bo peyvên ku çavkanîyên nivîskî tunin, mixabin gotinên devkî tene weke çavkanî dimînin. Pêşnîyara te ji bo pevên bi vî awayî çi ye gelo? Spas{{bêîmze|Xwende}}
::{{r|Xwende}} Pêşî li ser malpera [https://tirsik.net/xerhati.php] dûre jî li ser arsiv.org qeyd bike ku nikarî çavkanî peyda bikî. Herwiha bnr [[Alîkarî:Îmze]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:00, 15 îlon 2020 (UTC)
== [[şîloyî]] ==
{{r|Xwende}} ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:44, 14 îlon 2020 (UTC)
== [[dever]] ==
Ji bo wateya ''derence, sêlim û merdîwen'' [[Wîkîferheng:Pîvekên tevlîkirinê|çavkanî hewce ye]]. —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:53, 14 îlon 2020 (UTC)
== [[kerrnizî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:56, 15 îlon 2020 (UTC)
== [[li dora (yekê) gerîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:46, 15 îlon 2020 (UTC)
== [[glas]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]. --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 12:45, 16 îlon 2020 (UTC)
== [[kejmûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:42, 17 îlon 2020 (UTC)
== [[lirêgerîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:20, 18 îlon 2020 (UTC)
== [[çavpeqî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:23, 23 îlon 2020 (UTC)
:http://www.geocities.ws/mehname2004a/63/pexsan5.html -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 16:58, 23 îlon 2020 (UTC)
== [[çavpeqiyayî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:14, 23 îlon 2020 (UTC)
:{{r|Balyozxane}} Çavpeqî û -iyayî eynî peyv in, heke bo nimûne 2 çavkanî ji bo çavpeqî û 1 ji bo -iyayî werin dîtin ew weke 3 ji bo yek peyv tên jimartin? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:16, 23 îlon 2020 (UTC)
::{{r|Guherto}}. Na, divê yek ji wan peyitandî be daku em karibin yek ji wan wek guherto nîşan bidin. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:45, 23 îlon 2020 (UTC)
:::Temam naxwe. {{=(}} -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:48, 23 îlon 2020 (UTC)
::::Ecêb e peyva çavpeqîn jî tune ye. Divê çavpeqî/çavpeqiyayî jê hatibe lê qet tune. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:56, 23 îlon 2020 (UTC)
:::::Em çavpeqî ne weke kesekî ku çavê wan peqî ye dibêjin. Siqêfek e. ''Wilo neke, çavpeqî!'' -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 18:10, 23 îlon 2020 (UTC)
== [[olfatûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:58, 24 îlon 2020 (UTC)
:Du çavkanî hatiye dîtin li hêviya ya sisêyan e. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:46, 23 sibat 2025 (UTC)
== [[hekûke]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:00, 28 îlon 2020 (UTC)
:Tenê çavkaniyek ji Kovara Kurmancî heye ew jî ji kitêba Gundê Mêrxasan a Eliyê Evdilrehman hatiye girtin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:49, 23 sibat 2025 (UTC)
== [[dê bêjî kaya di serî de]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:58, 29 îlon 2020 (UTC)
== [[heçî ne li malekê, nizanit halekê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:58, 29 îlon 2020 (UTC)
== [[heta nîva destî reş nebit, tama devî xoş nabit]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:58, 29 îlon 2020 (UTC)
== [[kirtop]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Herwiha [[kirtope]]—[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:59, 29 îlon 2020 (UTC)
:
== [[pişkûmişk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:25, 29 îlon 2020 (UTC)
= Çiriya pêşîn 2020 =
== [[ma te şulê pê ye?]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:19, 1 çiriya pêşîn 2020 (UTC)
== [[zelam yêt heyn û zelamoşk jî yêt heyn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:28, 1 çiriya pêşîn 2020 (UTC)
== [[ezîz î; ji pêrve ne mîzî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:15, 1 çiriya pêşîn 2020 (UTC)
== [[qelpez]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:19, 6 çiriya pêşîn 2020 (UTC)
== [[firêzer]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:27, 6 çiriya pêşîn 2020 (UTC)
== [[kirmişk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 09:03, 7 çiriya pêşîn 2020 (UTC)
== [[orijîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:03, 7 çiriya pêşîn 2020 (UTC)
== [[şequdeq]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:50, 9 çiriya pêşîn 2020 (UTC)
== [[helizandin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:23, 9 çiriya pêşîn 2020 (UTC)
== [[rewastin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:48, 9 çiriya pêşîn 2020 (UTC)
== [[xunaftin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:31, 9 çiriya pêşîn 2020 (UTC)
== [[itîmad li ser kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 16:32, 15 çiriya pêşîn 2020 (UTC)
== [[sengesor]] ==
{{r|Saberabdollahizad}} ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:00, 16 çiriya pêşîn 2020 (UTC)
:Navçeya Mirgewerê, Ûrmîye. {{bêîmze|Saberabdollahizad}}
== [[zenek]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:03, 16 çiriya pêşîn 2020 (UTC)
:{{r|Saberabdollahizad}} [[zenek]] jî li Ûrmiyê tê bikaranîn? --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:53, 18 çiriya pêşîn 2020 (UTC)
== [[pind#p-mane|pind]] ==
Min tenê çavkaniyek dît ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:48, 17 çiriya pêşîn 2020 (UTC)
:Navçeya Mirgewerê, Ûrmîye. {{bêîmze|Saberabdollahizad}}
== [[maqezer]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:53, 18 çiriya pêşîn 2020 (UTC)
:Navçeya Mirgewerê, Ûrmîye. {{bêîmze|Saberabdollahizad}}
== [[pezpezok]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:55, 18 çiriya pêşîn 2020 (UTC)
:Navçeya Mirgewerê, Ûrmîye. {{bêîmze|Saberabdollahizad}}
== [[parapet#p-mane|parapet]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:53, 31 çiriya pêşîn 2020 (UTC)
= Çiriya paşîn 2020 =
== [[quloç#p-mane|quloç]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:43, 19 çiriya paşîn 2020 (UTC)
== [[di ber (yekî) de avêtin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:57, 20 çiriya paşîn 2020 (UTC)
:https://tirsik.net/index.php?mijarid=18749&iro=on&rupel=1 —[[Bikarhêner:Mamoste Dilovan|Mamoste Dilovan]] ([[Gotûbêja bikarhêner:Mamoste Dilovan|gotûbêj]]) 17:37, 20 çiriya paşîn 2020 (UTC)
:: {{r|Mamoste Dilovan}} [[di ber re avêtin]] heye. lê te wek di ber '''de''' lê zêde kir. Em biguherînin? Gelek çavkanî ji bo di ber re avêtin heye. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:41, 20 çiriya paşîn 2020 (UTC)
== [[hêvastin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:49, 21 çiriya paşîn 2020 (UTC)
== [[lamçîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:28, 22 çiriya paşîn 2020 (UTC)
== [[poz#p-mane|poz]] ==
Ji bo maneya "[[quz]], [[vajîna]]" ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:28, 22 çiriya paşîn 2020 (UTC)
== [[sihetbexş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:35, 24 çiriya paşîn 2020 (UTC)
== [[hevjêhatin ji jêhatîbûnê çêtir e]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:14, 25 çiriya paşîn 2020 (UTC)
== [[gerriyan#p-mane|gerriyan]] ==
''Ji bo wateya cereyan (tevger)''. Qasî ku dibînim tenê Cewerî bi kar aniye —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:24, 25 çiriya paşîn 2020 (UTC)
== [[derbend]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:08, 30 çiriya paşîn 2020 (UTC)
= Kanûna paşîn 2021 =
== [[desthelgirtin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:48, 6 kanûna paşîn 2021 (UTC)
== [[êşewî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:52, 7 kanûna paşîn 2021 (UTC)
== [[sexorr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:22, 7 kanûna paşîn 2021 (UTC)
== [[sapût]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:06, 12 kanûna paşîn 2021 (UTC)
= Çiriya pêşîn 2022 =
== [[paşbingeh]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:40, 12 çiriya pêşîn 2022 (UTC)
= Kanûna paşîn 2023 =
== [[berê beringa]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:35, 1 kanûna paşîn 2023 (UTC)
== [[mûlûleya xwînê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:36, 6 kanûna paşîn 2023 (UTC)
== [[mirovdostî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 05:38, 12 kanûna paşîn 2023 (UTC)
== [[derîner]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:02, 15 kanûna paşîn 2023 (UTC)
== [[neteba]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:01, 16 kanûna paşîn 2023 (UTC)
:Maneya "uyruksuz" ji ferhenga Farqînî hatiye kopîkirin wekî din bi giştî bi maneya "nakokî" tê bikaranîn. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:33, 26 hezîran 2026 (UTC)
== [[narkotîkfiroş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:59, 16 kanûna paşîn 2023 (UTC)
== [[pirik#p-mane|pirik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:15, 16 kanûna paşîn 2023 (UTC)
== [[ramyarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ez dibêjim ev peyv tenê bi maneya "fikrî", wek rengdêr heye. Lê belê di soranî de bi şaşemilandineke wekî "siyasî" bi kar anîne, kurmancên me jî rasterast maneya soranî tevlî çend ferhengên hev-kopîkirî kirine û bi vî awayî belkî hin çavkanî hebin. Lê ez dibêjim divê ew çavkanî jî neyên hesibandin. Hin kes zen dikin ku her tiştê di soranî de ripîrast e. Mesele heqeten eletewş e.
Agahiyeke li ser peyvê: "[https://www.welateme.info/kurdi/modules.php?name=News&file=print&sid=686 Welateme.info]"
Li vir peyv bi maneya "fikrî" hatiye bikaranîn: "[https://xwebun1.org/ziman-sirhelal-e/ Xwebûn]". Qewet be. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:36, 19 kanûna paşîn 2023 (UTC)
== [[dolîwgeh]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:08, 22 kanûna paşîn 2023 (UTC)
= Sibat 2023 =
== [[herebaş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:13, 11 sibat 2023 (UTC)
== [[balindeyê korare]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].''
Tenê çavkaniyekê tê xuyan, ew jî kitêba "Balindeyên Kurdistanê" yê Îsmaîl Taha Şahîn e (Îsmaîl Taha Şahîn (2006). Balindeyên Kurdistanê, 1-4. Spîrêz, Duhok). Ev kitêb ji ser înternetê wek word jî peyda dibe. Xuya ye ku ji Weşanên Spîrez fîzîken jî çap bûye. Lê wekî çavkanî tenê ev tê xuyan.
—[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:10, 14 sibat 2023 (UTC)
== [[dîbeyt]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:13, 18 sibat 2023 (UTC)
== [[çend pat]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:35, 24 sibat 2023 (UTC)
== [[rabînîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ji bo kurmancî bêçavkanî ye. Ger ji bo soranî çavkanî hebin jî divê ji bo soranî bi herfên aramî were nivîsîn.
An ger halê latînîkirî ya peyveke soranî be, dîsa divê ev hal were eşkerekirin. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:36, 26 sibat 2023 (UTC)
= Adar 2023 =
== [[qirar#p-mane|qirar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:23, 2 adar 2023 (UTC)
== [[pîşpêkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Tenê di nivîseke online ya Cemal Nebez de derbas dibe. Dibe ku peyveke başûriyan be. Û di wê nivîsa Cemal Nebezê bi forma "pîş bi ... bikin" jî derbas dibe. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:29, 3 adar 2023 (UTC)
== [[kûcag]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:27, 27 adar 2023 (UTC)
== [[destbizavî kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].''
Xuya ye ku ev peyv tenê di kitêba “''Nameyek Şernexê''” yê Evdirehman Gundikî de derbas bûye. Û ji wir jî Mistefa Aydoganî maneya peyvê di hejmara 61em ya Kovara ''Kurmancî'' de belav kiriye (rûpela 2yem). Lê belê tenê yek çavkaniyekê ne bes e ji bo ku em vê peyvê di vê ferhengê de wekî peyveke çavkanî-temam belav bikin. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:40, 28 adar 2023 (UTC)
== [[germav baş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:23, 31 adar 2023 (UTC)
== [[germavxweş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:27, 31 adar 2023 (UTC)
= Gulan 2023 =
== [[kirbend]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:34, 5 gulan 2023 (UTC)
: Çêkerê rûpelê wek not nivîsiye ku peyv ji herêma Çewlikê ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:24, 6 gulan 2023 (UTC)
== [[We are off now]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:13, 22 gulan 2023 (UTC)
== [[baran ji erd û esmana lê diket]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:46, 26 gulan 2023 (UTC)
== [[sîba]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:44, 30 gulan 2023 (UTC)
== [[bîrîner]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:20, 30 gulan 2023 (UTC)
= Hezîran 2023 =
== [[bidestmiz]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:49, 10 hezîran 2023 (UTC)
= Tîrmeh 2023 =
== [[parzeng]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:01, 15 tîrmeh 2023 (UTC)
== [[zerpedest]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:05, 15 tîrmeh 2023 (UTC)
== [[heta destên mirov nebine gû, devê mirov nabite tû]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:27, 19 tîrmeh 2023 (UTC)
== [[çemê mezin tê dibore diçe]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:38, 19 tîrmeh 2023 (UTC)
== [[kevanê nebî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:45, 19 tîrmeh 2023 (UTC)
== [[teybûr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Tenê li malpera yuksekovahaber.com-ê derbas dibe, li wir jî bi maneya "çare, çareserî"yê hatiye emilandin, ne bi vê maneyê. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:00, 19 tîrmeh 2023 (UTC)
== [[nikêtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:22, 19 tîrmeh 2023 (UTC)
= Tebax 2023 =
== [[prêze]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:19, 15 tebax 2023 (UTC)
== [[qelye]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:59, 25 tebax 2023 (UTC)
= Îlon 2023 =
== [[hetwan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:21, 14 îlon 2023 (UTC)
== [[milyonmasî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:06, 21 îlon 2023 (UTC)
== [[tofana mêjî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:20, 21 îlon 2023 (UTC)
== [[îshaf]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:57, 23 îlon 2023 (UTC)
== [[odeya karkirinê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:57, 26 îlon 2023 (UTC)
== [[odeya şixulînê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:42, 26 îlon 2023 (UTC)
== [[hîter]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:08, 26 îlon 2023 (UTC)
= Çiriya pêşîn 2023 =
== [[vîtamîn waw]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:20, 1 çiriya pêşîn 2023 (UTC)
== [[berhan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:21, 3 çiriya pêşîn 2023 (UTC)
== [[sêlikdêr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:16, 10 çiriya pêşîn 2023 (UTC)
== [[qewandêr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:20, 10 çiriya pêşîn 2023 (UTC)
== [[darav]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:04, 11 çiriya pêşîn 2023 (UTC)
= Çiriya paşîn 2023 =
== [[kansernasî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:54, 14 çiriya paşîn 2023 (UTC)
== [[golgir]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:06, 15 çiriya paşîn 2023 (UTC)
== [[ahardar]] ==
Tenê Zana Farqînî —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:34, 26 çiriya paşîn 2023 (UTC)
= Kanûna pêşîn 2023 =
== [[Kî mal ezîz bike, can rezîl dike; kî mal rezîl bike, can ezîz dike.]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:55, 2 kanûna pêşîn 2023 (UTC)
== [[porsing]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:22, 2 kanûna pêşîn 2023 (UTC)
== [[newelatî kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:57, 2 kanûna pêşîn 2023 (UTC)
== [[newelatîkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:58, 2 kanûna pêşîn 2023 (UTC)
== [[alîkar kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 18:42, 3 kanûna pêşîn 2023 (UTC)
== [[aramoş kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' -- [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 18:48, 3 kanûna pêşîn 2023 (UTC)
== [[bêvil kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 18:56, 4 kanûna pêşîn 2023 (UTC)
== [[dijberiya naverokî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 22:43, 8 kanûna pêşîn 2023 (UTC)
== [[bersivîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'', û her wiha ne li gorî qayideyên kurdî (kurmancî) ye. —[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 22:08, 11 kanûna pêşîn 2023 (UTC)
== [[hevalzewac]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:25, 18 kanûna pêşîn 2023 (UTC)
= Kanûna paşîn 2024 =
== [[raperk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:40, 15 kanûna paşîn 2024 (UTC)
:Bi wê manê nîne. Bi maneke dî heye. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:26, 6 tebax 2024 (UTC)
= Sibat 2024 =
== [[bi navê lîstin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:02, 3 sibat 2024 (UTC)
: Li ser agahdarkirina çêkerê rûpelê me şablona "dever"ê tevlî kir. Peyv ji herêma Qoser, Mêrdînê ye. Me li qismê "Bikaranîn"ê jî vê yekê dîyar kir. Ji xeynî vê me wê peyvê ji rûpelên din jî bir. Bi vî awayî ev made kare were girtin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:28, 10 nîsan 2024 (UTC)
== [[preskirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:43, 11 sibat 2024 (UTC)
== [[pres kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:43, 11 sibat 2024 (UTC)
== [[Xwedê te têr bike]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:41, 13 sibat 2024 (UTC)
== [[isad]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:09, 22 sibat 2024 (UTC)
== [[razemendîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:44, 27 sibat 2024 (UTC)
= Adar 2024 =
== [[zilhêzî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:19, 5 adar 2024 (UTC)
== [[hingihîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:32, 13 adar 2024 (UTC)
== [[xore]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:30, 15 adar 2024 (UTC)
== [[melef]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:55, 19 adar 2024 (UTC)
== [[uğursuz ayak]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:11, 23 adar 2024 (UTC)
== [[şepûk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:22, 23 adar 2024 (UTC)
== [[agirberdêrî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:04, 26 adar 2024 (UTC)
== [[kîrquz]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:00, 26 adar 2024 (UTC)
== [[kîrquzdê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:01, 26 adar 2024 (UTC)
== [[quzgêj]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:03, 26 adar 2024 (UTC)
= Nîsan 2024 =
== [[hîkarîkerî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:53, 1 nîsan 2024 (UTC)
== [[hîkarîker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:54, 1 nîsan 2024 (UTC)
== [[hîkarîdarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:55, 1 nîsan 2024 (UTC)
== [[hîkarîdar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:56, 1 nîsan 2024 (UTC)
== [[bihîkarîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:57, 1 nîsan 2024 (UTC)
== [[bihîkarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:58, 1 nîsan 2024 (UTC)
== [[bêhîkarîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:58, 1 nîsan 2024 (UTC)
== [[bêhîkarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:59, 1 nîsan 2024 (UTC)
== [[bêhîkarî bûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:00, 1 nîsan 2024 (UTC)
== [[bêhîkarî kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:00, 1 nîsan 2024 (UTC)
== [[bêhîkarîbûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:01, 1 nîsan 2024 (UTC)
== [[bêhîkarîkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:01, 1 nîsan 2024 (UTC)
== [[hîkarîkirî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:02, 1 nîsan 2024 (UTC)
== [[hîkarîkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:03, 1 nîsan 2024 (UTC)
== [[hîkarî kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:07, 1 nîsan 2024 (UTC)
== [[rayda]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:40, 1 nîsan 2024 (UTC)
== [[bêrayda]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:42, 1 nîsan 2024 (UTC)
== [[bêraydayî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:43, 1 nîsan 2024 (UTC)
== [[bêraydatî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:43, 1 nîsan 2024 (UTC)
== [[bêrayda bûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:43, 1 nîsan 2024 (UTC)
== [[bêrayda kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:43, 1 nîsan 2024 (UTC)
== [[bêraydabûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:44, 1 nîsan 2024 (UTC)
== [[bêraydakirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:44, 1 nîsan 2024 (UTC)
== [[birayda]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:44, 1 nîsan 2024 (UTC)
== [[biwerêç]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:46, 1 nîsan 2024 (UTC)
== [[biwerêçî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:46, 1 nîsan 2024 (UTC)
== [[biwerêçîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:47, 1 nîsan 2024 (UTC)
== [[biwerêçtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:47, 1 nîsan 2024 (UTC)
== [[werêç]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:49, 1 nîsan 2024 (UTC)
== [[werêç kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:50, 1 nîsan 2024 (UTC)
== [[werêçkirî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:50, 1 nîsan 2024 (UTC)
== [[werêçkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:51, 1 nîsan 2024 (UTC)
== [[wişto]] ==
@[[Bikarhêner:Zerwanda]] Çavkanî ji bo kurmancî tine ye. Zazakî ye? —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:19, 1 nîsan 2024 (UTC)
:ji devera mûşê, aliyê diya min "wişto/wiştik" digotin. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 20:44, 1 nîsan 2024 (UTC)
== [[xewgerî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:19, 2 nîsan 2024 (UTC)
== [[xewgerokî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:28, 2 nîsan 2024 (UTC)
== [[nimêh]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:51, 2 nîsan 2024 (UTC)
== [[فعال]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' [[Bikarhêner:Ghybu|Gyhbu]] di 2021an de li ser rûpela wî tevlî kirîye. Lê ji ber nebûna vê sîstema me tevlî vir nebûye. Lewma min anî ser vir jî. Wê gavê tenê ji bo maneya "kar" ev şablon hatîye tevlîkirin. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:27, 3 nîsan 2024 (UTC)
== [[pêjor]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:46, 6 nîsan 2024 (UTC)
== [[hafil]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:48, 6 nîsan 2024 (UTC)
== [[micemi]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Muhtemelem şiklê rast wek "micemî" ye, lê ew jî bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:52, 9 nîsan 2024 (UTC)
== [[hesaba xwe]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ji xeynî van ev forma wê jî bêçavkanî ne: "[[hisaba xwe]] / [[hesaba xo]] / [[hisaba xo]]". — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:25, 9 nîsan 2024 (UTC)
== [[leşkerma]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:53, 24 nîsan 2024 (UTC)
= Gulan 2024 =
== [[serîi]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Wek "serî" belkî li kar be, lê wekî "serîi" çavkanî peyda nabe. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:20, 2 gulan 2024 (UTC)
== [[virgu]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Bêçavkanî tê xuyan. Peyv bi tirkî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:49, 2 gulan 2024 (UTC)
== [[xwerre]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:57, 20 gulan 2024 (UTC)
= Hezîran 2024 =
== [[di nav deryaya peyvan de avjenî dikir]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:34, 4 hezîran 2024 (UTC)
== [[berbixwe]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:03, 10 hezîran 2024 (UTC)
== [[şipyakî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:08, 10 hezîran 2024 (UTC)
:"şipêkî" heye [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:20, 27 tîrmeh 2024 (UTC)
== [[wetalane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:18, 10 hezîran 2024 (UTC)
== [[wetalî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:19, 10 hezîran 2024 (UTC)
== [[wetalîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:20, 10 hezîran 2024 (UTC)
== [[wetaltî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:20, 10 hezîran 2024 (UTC)
== [[se direye karwan dibuhire]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:49, 16 hezîran 2024 (UTC)
== [[se direwît, karwan bi rêka xo diçît]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Formeke bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:53, 16 hezîran 2024 (UTC)
== [[se direwît, karwan diburît]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Formeke bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:54, 16 hezîran 2024 (UTC)
== [[se direyê karwan dibûrê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Formeke bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:55, 16 hezîran 2024 (UTC)
== [[se direye karvan li riya xwe diçe]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Formeke bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:56, 16 hezîran 2024 (UTC)
== [[se direye û kerwan dibihure]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Formeke bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:00, 16 hezîran 2024 (UTC)
== [[hingê dinya]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:07, 18 hezîran 2024 (UTC)
== [[berdanberdan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:22, 24 hezîran 2024 (UTC)
== [[nîska mala Barzan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:05, 25 hezîran 2024 (UTC)
= Tîrmeh 2024 =
== [[avmêwe]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:41, 16 tîrmeh 2024 (UTC)
== [[nihalok]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:53, 24 tîrmeh 2024 (UTC)
== [[nihalk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:54, 24 tîrmeh 2024 (UTC)
== [[niwalok]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:54, 24 tîrmeh 2024 (UTC)
== [[niwalk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:55, 24 tîrmeh 2024 (UTC)
== [[niwalik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:55, 24 tîrmeh 2024 (UTC)
== [[nehalok]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:56, 24 tîrmeh 2024 (UTC)
== [[nehalk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:56, 24 tîrmeh 2024 (UTC)
== [[nehalik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:57, 24 tîrmeh 2024 (UTC)
== [[alîkarxwazî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:16, 24 tîrmeh 2024 (UTC)
== [[nihalik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:19, 24 tîrmeh 2024 (UTC)
== [[zêrîb]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Bi dîtina min, ev form bêçavkanî ye. Dibe ku ji bo forma "zerîb" çavkanî hebe, lê ew form li ser Wîkîferhengê wek made nehatîye çêkirin. Pir kêm be jî ev peyv wek "zerîp" jî li cihekî derbas bûye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:31, 24 tîrmeh 2024 (UTC)
== [[qanat]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ev peyv bi maneya "kanal"ê nîne, xelet e. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:59, 25 tîrmeh 2024 (UTC)
== [[qenat]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ev peyv bi maneya "kanal"ê nîne, xelet e. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:59, 25 tîrmeh 2024 (UTC)
:Min 1 çavkanî dît, "Ev kes rojê 6-7 qenatên TV ya diğere û diaxive" https://darkamazi.info/archives/1019704 (bi lêgerana "qenatên", ji ber ko lêgeranên dî tijî tiştên dî derdixin)
:Lê mimkun e xelet nivîsîbe. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:31, 27 tîrmeh 2024 (UTC)
== [[şkaftmend]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:08, 25 tîrmeh 2024 (UTC)
:3 çavkanî hene. Lê xuya dike jêdera wan hemû ferhenga tirsik.netê e.
:* Çavkanî 1: https://tirsik.net/ferheng?query=şkaftmend (berî 2014, lê tarîx nîne)
:* Çavkanî 2: https://ku.wikipedia.org/wiki/Deranderî (2011)
:* Çavkanî 3: https://x.com/zerdeceno/status/1155331427510247425 (2019)
:[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:28, 27 tîrmeh 2024 (UTC)
== [[dan hev#p-mane|dan hev]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ev mane li ser çavkanîyan peyda nabe. Tenê çêkerê rûpelê vê maneyê nivîsîye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:08, 25 tîrmeh 2024 (UTC)
== [[baristdar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:47, 25 tîrmeh 2024 (UTC)
== [[baristdarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:48, 25 tîrmeh 2024 (UTC)
== [[baristdarîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:49, 25 tîrmeh 2024 (UTC)
== [[baristdartî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:50, 25 tîrmeh 2024 (UTC)
== [[bibaristî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:51, 25 tîrmeh 2024 (UTC)
== [[bibarist]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:52, 25 tîrmeh 2024 (UTC)
== [[bibaristîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:53, 25 tîrmeh 2024 (UTC)
== [[bêbaristî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:54, 25 tîrmeh 2024 (UTC)
== [[bêbarist]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:55, 25 tîrmeh 2024 (UTC)
== [[peydabûnîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:52, 27 tîrmeh 2024 (UTC)
== [[nexşvan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:05, 27 tîrmeh 2024 (UTC)
== [[nexşvanî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:05, 27 tîrmeh 2024 (UTC)
== [[lêvikî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:47, 27 tîrmeh 2024 (UTC)
== [[lêvik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Min zêde nemeyzand. —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:48, 27 tîrmeh 2024 (UTC)
== [[şerenîx kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:51, 27 tîrmeh 2024 (UTC)
== [[sala çîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:09, 29 tîrmeh 2024 (UTC)
== [[berê pêşîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:57, 30 tîrmeh 2024 (UTC)
== [[berê ewilî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:57, 30 tîrmeh 2024 (UTC)
= Tebax 2024 =
== [[xwîndijmin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:02, 4 tebax 2024 (UTC)
== [[reîsîtî]]/[[reysîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 18:17, 6 tebax 2024 (UTC)
== [[soranîaxivî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 18:43, 6 tebax 2024 (UTC)
== [[helîna bildo]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ji 2020a ve şablon li wir e min tenê li vir zêde kir. —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:56, 6 tebax 2024 (UTC)
== [[hilbijêrînerî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' "Hilbij" rihê peyvê ye, "-er" jî maneya "kesê dike" dide, qertafa "-îner / -înerî" ya li dawîyê bi eşkereyî xelet e. Û ev peyv bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:45, 7 tebax 2024 (UTC)
== [[îqlîmtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:43, 10 tebax 2024 (UTC)
== [[jîrbêjî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ev peyv ger di soranî de ji bo wê çavkanîyên têr hebe, tenê wek "Nivîsîna bi herfên latînî ya peyva ..." kare were qeydkirin. Lê nabe ku wek kurmancî be, were qeydkirin. Ji bo kurmancî tenê di meqaleyekê de derbas dibe, ew jî ji alî başûrîyekî ve hatîye nivîsîn. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:55, 10 tebax 2024 (UTC)
== [[hîgyen]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Tenê ev form bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:40, 12 tebax 2024 (UTC)
== [[kolîner]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:45, 12 tebax 2024 (UTC)
== [[poşmanî kişîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Tenê di çavkanîyekê de peyda dibe. Ew çavkanîya dayî jî ihtîmal e ku bi tesîra zimanê tirkî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:44, 13 tebax 2024 (UTC)
== [[para hevpar a herî mezin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:09, 14 tebax 2024 (UTC)
== [[cara hevpar a herî biçûk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:10, 14 tebax 2024 (UTC)
== [[regjimar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:16, 14 tebax 2024 (UTC)
== [[nîvrengî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:33, 18 tebax 2024 (UTC)
== [[nîvdûrgeh]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Wek "guharto" hatiye qeydkirin, lê yek nikare wilo bêje. Ne "guharto" ye, ji ber wê çavkanî hewce ne. —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:39, 18 tebax 2024 (UTC)
== [[nîvtewlîsî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:44, 18 tebax 2024 (UTC)
== [[nîvrewa]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:44, 18 tebax 2024 (UTC)
== [[nîvşehtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:46, 18 tebax 2024 (UTC)
== [[nîvfalincî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:46, 18 tebax 2024 (UTC)
== [[nîvgilkarî]] ==
Ferhenga Sa.Sa.
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:48, 18 tebax 2024 (UTC)
== [[kesîkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:16, 18 tebax 2024 (UTC)
== [[biryarbirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:38, 24 tebax 2024 (UTC)
= Îlon 2024 =
== [[qîblename]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:15, 1 îlon 2024 (UTC)
== [[cihetnima]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].''
Ji [https://bianet.org/yazi/ferhenga-ser-u-amrazen-ser-ji-kovara-ronahiye-2-196299 vir] hatiye —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:27, 1 îlon 2024 (UTC)
== [[qûnderîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:36, 7 îlon 2024 (UTC)
== [[hevpîşehî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:48, 14 îlon 2024 (UTC)
== [[hevpîşeyî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Wek ''navdêr'' bêçavkanî ye. Wek ''rengdêr'' hebe jî, kêmçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:50, 14 îlon 2024 (UTC)
== [[hempay]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:04, 14 îlon 2024 (UTC)
== [[nîs]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:43, 14 îlon 2024 (UTC)
== [[êxbarçî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ji bo ''îxbarçî'' çavkanîyekê peyda dibe, lê kêm e. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:06, 14 îlon 2024 (UTC)
== [[muxbîrîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:38, 14 îlon 2024 (UTC)
== [[ixbarçî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:43, 14 îlon 2024 (UTC)
== [[westgeha pasê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:33, 19 îlon 2024 (UTC)
== [[avikmadk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:24, 19 îlon 2024 (UTC)
== [[ferwerîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:18, 20 îlon 2024 (UTC)
== [[bipayderî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:12, 20 îlon 2024 (UTC)
== [[bipayderîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:13, 20 îlon 2024 (UTC)
== [[bipaydertî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:13, 20 îlon 2024 (UTC)
== [[bipedalî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:14, 20 îlon 2024 (UTC)
== [[bipedalîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:14, 20 îlon 2024 (UTC)
== [[bipedaltî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:14, 20 îlon 2024 (UTC)
== [[pedaldarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:15, 20 îlon 2024 (UTC)
== [[pedaldarîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:15, 20 îlon 2024 (UTC)
== [[pedaldartî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:16, 20 îlon 2024 (UTC)
== [[ciwên]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:11, 22 îlon 2024 (UTC)
== [[belgebirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:31, 23 îlon 2024 (UTC)
== [[avsîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Dibe ku ev kelîme biçavkanî be, lê pir bêagahî hatîye qeydkirin. Biçavkanî be jî, ji ber qeydkirina bêagahî (li ser madeyê agahî kêm hatine pêşkêşkirin) em rûpelê jê bibin. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:53, 23 îlon 2024 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] Ma ne çêtir e li "Daxwazên zelalkirinê" be naxwe? [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 13:40, 10 çiriya paşîn 2024 (UTC)
:: {{r|Guherto}} Bi min em karin vê madeyê ji ber bêagahîbûna wî, îlaweyî "Daxwazên jêbirinê" bikin, lê "Daxwazên zelalkirinê" nabe. Wekî din ji ber ku dikeve çarçoveya her du rûpelan jî kîjan be jî dibe, lê madem me li vir da destpêkirin, bila ji vir dewam bike dengdana li ser vê kelîmeyê. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:22, 10 çiriya paşîn 2024 (UTC)
== [[kûvank]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Herçiqas tenê formekê be jî, bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:14, 27 îlon 2024 (UTC)
== [[kikark]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Herçiqas formekê be jî, bêçavkanî dixuye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:40, 27 îlon 2024 (UTC)
== [[kîfkarik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Herçiqas tenê formekê be jî, bêçavkanî dixuye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:45, 27 îlon 2024 (UTC)
== [[kuvank]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Herçiqas formekê be jî, bêçavkanî dixuye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:53, 27 îlon 2024 (UTC)
== [[kûvarik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Herçiqas tenê formekê be jî, bêçavkanî dixuye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:01, 27 îlon 2024 (UTC)
= Çiriya pêşîn 2024 =
== [[muhil]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Tenê qismê kurdî bêçavkanî ye, ne ya tirkî. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:00, 9 çiriya pêşîn 2024 (UTC)
== [[muhilk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:00, 9 çiriya pêşîn 2024 (UTC)
== [[jêrmal]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:59, 15 çiriya pêşîn 2024 (UTC)
= Çiriya paşîn 2024 =
== [[bîrxistiner]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:38, 14 çiriya paşîn 2024 (UTC)
== [[wisa gotin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ne ferhengî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:13, 15 çiriya paşîn 2024 (UTC)
== [[tenzîlatî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:12, 16 çiriya paşîn 2024 (UTC)
== [[armancparêz]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:03, 19 çiriya paşîn 2024 (UTC)
== [[şikaftmend]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:06, 19 çiriya paşîn 2024 (UTC)
== [[derander]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:08, 19 çiriya paşîn 2024 (UTC)
== [[deranderî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:09, 19 çiriya paşîn 2024 (UTC)
== [[derxwenîn]] ==
@[[User:Keyomerd]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:38, 19 çiriya paşîn 2024 (UTC)
== [[kiyû]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:53, 22 çiriya paşîn 2024 (UTC)
== [[goçavik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:03, 23 çiriya paşîn 2024 (UTC)
== [[hûrhesibaner]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:19, 23 çiriya paşîn 2024 (UTC)
== [[heybera zindî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:01, 23 çiriya paşîn 2024 (UTC)
== [[misadereker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:51, 24 çiriya paşîn 2024 (UTC)
== [[kêmtirkar]] ==
@[[Bikarhêner:Cyrus the virus|Cyrus the virus]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:11, 24 çiriya paşîn 2024 (UTC)
== [[cudagir]] ==
@[[Bikarhêner:Cyrus the virus|Cyrus the virus]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:15, 24 çiriya paşîn 2024 (UTC)
== [[qerwaşgir]] ==
@[[Bikarhêner:Cyrus the virus|Cyrus the virus]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:42, 24 çiriya paşîn 2024 (UTC)
== [[mişkilûr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:45, 24 çiriya paşîn 2024 (UTC)
== [[agirbiran]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:54, 24 çiriya paşîn 2024 (UTC)
== [[alan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:55, 24 çiriya paşîn 2024 (UTC)
== [[alêdû]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:56, 24 çiriya paşîn 2024 (UTC)
== [[henaseker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:37, 25 çiriya paşîn 2024 (UTC)
== [[nial]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ji xeynî vê, maneya 2yem ya di madeya "nihal" de (navdêr 2) jî bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:46, 27 çiriya paşîn 2024 (UTC)
== [[gilçim]] ==
@[[Bikarhêner:Roj Rewandiz|Roj Rewandiz]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:28, 29 çiriya paşîn 2024 (UTC)
:Ez ji bajarê Kobaniyê me. Min ev peyv ji ser zimanê dê û dapîra xwe girt. Ez gelekî ji vê peyvê bawer im ji ber ku devoka min e. [[Bikarhêner:Roj Rewandiz|Roj Rewandiz]] ([[Gotûbêja bikarhêner:Roj Rewandiz|gotûbêj]]) 19:14, 23 kanûna pêşîn 2024 (UTC)
::@[[Bikarhêner:Roj Rewandiz|Roj Rewandiz]] Mala te ava. Ji ber ku çavkaniyên nivîskî tine ne em ê vê kelîmeyê wek [[Wîkîferheng:Pîvekên_tevlîkirinê#Peyvên_herêmî|herêmî]] nîşan bidin. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:22, 25 kanûna pêşîn 2024 (UTC)
== [[reşeme kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Li gorî çêkera sayfeyê li Kobaniyê li kar e. Loma dikare wekî herêmî nîşankirin —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:35, 29 çiriya paşîn 2024 (UTC)
:Li Kobaniyê dibêjin: ‘Tizbiyê min li xwe reş û reşeme kir’ Anko Tizbî bir û venegerand Înkar kir. [[Bikarhêner:Roj Rewandiz|Roj Rewandiz]] ([[Gotûbêja bikarhêner:Roj Rewandiz|gotûbêj]]) 23:48, 7 kanûna paşîn 2025 (UTC)
= Kanûna pêşîn 2024 =
== [[çarkunc]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:21, 27 kanûna pêşîn 2024 (UTC)
= Kanûna paşîn 2025 =
== [[pevnatî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ev kelîme wek kurtkirina ji "bi hev nehatî" hatîye fikirîn, ihtimal e ku wek sunî hatîye çêkirin û bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:43, 6 kanûna paşîn 2025 (UTC)
== [[lihevnatîbûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Wek formeke "lihevnehatîbûn"ê hatîye fikirîn, lê bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:49, 6 kanûna paşîn 2025 (UTC)
== [[Binxetê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Em zanin ku ji ber sînora Tirkîyeyê ku Cizîrê dike du perçe û ji parçeya binî re Binxet tê gotin ku kurd lê dijîn. Feqet min herêma Binxetê seh nekirîye, û bi tedqîqeke neberfireh jî çavkanî peyda nekir ji bo vê peyvê. Lewma bi min mahîyeta vê madeyê û qeydkirina wek "Binxetê" xelet e. Ger ji bo herêma tarîfkirî çavkanî nîn be, em vê madeyê bikin "'''Binxet'''" û tarîfa wê jî bikin "''Navê Rojavayê Kurdistanê ku li binê xeta Tirkîye - Sûrîyeyê dimîne.''" — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:22, 24 kanûna paşîn 2025 (UTC)
== [[xirecir çêkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' "''Çê''" bi maneya "''baş''" e, û "''çêkirin''" ji bo tiştên pozîtîv likar e, li nav milet jî ev wisa ye. Lewma ev xelet e. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:00, 24 kanûna paşîn 2025 (UTC)
== [[sîrkxane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:30, 26 kanûna paşîn 2025 (UTC)
== [[anînder]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Di hin çavkanîyan de wek formeke "''der'''''anîn'''"ê hatîye qeydkirin. Lê belê çavkanîyeke eslî (ne ji kitêbên gramerê) peyda nabe ji bo vê kelîmeyê. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:24, 27 kanûna paşîn 2025 (UTC)
== [[rastî nîşandan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:14, 30 kanûna paşîn 2025 (UTC)
= Sibat 2025 =
== [[pir hindik#p-mane|pir hindik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:31, 3 sibat 2025 (UTC)
== [[anteroz]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:19, 15 sibat 2025 (UTC)
== [[veşidandin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:43, 15 sibat 2025 (UTC)
== [[karîwer kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 02:01, 16 sibat 2025 (UTC)
== [[hil girtin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:06, 17 sibat 2025 (UTC)
== [[wer girtin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:07, 17 sibat 2025 (UTC)
== [[avkûre]] ==
@[[Bikarhêner:Mohajeer|Mohajeer]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:36, 18 sibat 2025 (UTC)
== [[derdek girtin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:42, 18 sibat 2025 (UTC)
== [[vekitandin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:12, 21 sibat 2025 (UTC)
== [[dînperist]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:32, 22 sibat 2025 (UTC)
== [[ayînperist]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:33, 22 sibat 2025 (UTC)
== [[ayînperistî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:33, 22 sibat 2025 (UTC)
== [[ayînperistane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:33, 22 sibat 2025 (UTC)
== [[piştrastkirinbar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:45, 23 sibat 2025 (UTC)
== [[diyarkirinbar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 02:08, 23 sibat 2025 (UTC)
== [[hevhatî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 03:52, 1 adar 2025 (UTC)
== [[kerexne]] ==
@[[Bikarhêner:Rojhilat|Rojhilat]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:05, 1 adar 2025 (UTC)
== [[pawan#p-mane|pawan]] ==
Ferhenga Hejar soranî ye, ji bo maneya "cihê qedexekirî, cihê inhisarî" ji xeynî wê çavkanî heye gelo —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:17, 3 adar 2025 (UTC)
== [[taqane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 02:11, 6 adar 2025 (UTC)
== [[gosirmetî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:07, 6 adar 2025 (UTC)
== [[matîjêker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:08, 14 adar 2025 (UTC)
== [[hilewesan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:14, 16 adar 2025 (UTC)
== [[peydandin#p-mane|peydandin]] ==
Ji bo maneya [[dûre]], [[paşî]], [[piştî]] wê / wî ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:26, 19 adar 2025 (UTC)
== [[peşar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:36, 19 adar 2025 (UTC)
== [[nûzaf]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:30, 20 adar 2025 (UTC)
== [[deynname]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:08, 20 adar 2025 (UTC)
== [[girtek]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:29, 20 adar 2025 (UTC)
== [[bêşxane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:25, 23 adar 2025 (UTC)
== [[superleheng]] ==
@[[Bikarhêner:Avestaboy|Avestaboy]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:34, 23 adar 2025 (UTC)
== [[goranparêz]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:54, 27 adar 2025 (UTC)
== [[raber#p-mane|raber]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:09, 1 nîsan 2025 (UTC)
== [[morîvanî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:06, 1 nîsan 2025 (UTC)
== [[wevde]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:17, 3 nîsan 2025 (UTC)
== [[şandêr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:31, 3 nîsan 2025 (UTC)
== [[giloverî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Tenê li ser Ferhenga Zaravên Teknîkî û hejmara yekem ya kovara Bernamegehê derbas dibe. —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:10, 4 nîsan 2025 (UTC)
== [[xêvjimêr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:00, 12 nîsan 2025 (UTC)
== [[paşekeft]] ==
@[[Bikarhêner:Jiyar Hesen|Jiyar Hesen]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:11, 13 nîsan 2025 (UTC)
== [[paşekeft kirin]] ==
@[[Bikarhêner:Jiyar Hesen|Jiyar Hesen]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:12, 13 nîsan 2025 (UTC)
== [[hawirkanî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:36, 20 nîsan 2025 (UTC)
== [[hawirkan]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:18, 21 nîsan 2025 (UTC)
: Di çavkanîyeke de "''hawrikan''" (ku wek forma vê kelîmeyê hatîye nîşandayîn û wek "hevrêyan" a kurmancî ye) bi maneya "''friends''"a îngilîzî hatîye emilandin. Wekî din ti çavkanî nîne ji bo vê peyvê. Ger ev "hevrik"a kurmancî be, li vir dîsa xelet e. Bi her awayî divê were jêbirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:28, 21 nîsan 2025 (UTC)
== [[hawirkanî]] ==
@[[Bikarhêner:Cyrus the virus|Cyrus the virus]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:18, 21 nîsan 2025 (UTC)
== [[hawirkanvanî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:18, 21 nîsan 2025 (UTC)
== [[riberiz]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:39, 21 nîsan 2025 (UTC)
:Ez bawer im ku ev hat peyitandin. Min çavkanî di beşa gotûbêjê de dan û şablona bêçavkanîtiyê rakir. [[Bikarhêner:Ferhengvan|Ferhengvan]] ([[Gotûbêja bikarhêner:Ferhengvan|gotûbêj]]) 06:11, 25 nîsan 2025 (UTC)
== [[awam]] ==
@[[User:Ferhengvan|Ferhengvan]] Min ev kelîme di ferhengên din de nedît. Gelo di kurmancî de tê bikaranîn? —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:09, 23 nîsan 2025 (UTC)
:@[[Bikarhêner:Zerwanda|Zerwanda]] tu jî forma [[vam]] ya vê kelîmeyê diemilînî, gelo çavkanî ji bo wê jî heye? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:11, 23 nîsan 2025 (UTC)
::Min jî li vir peyda kir û dest kar anîn kir, hemin ev li guherrên dengî ên Kurmancî dihat (*ebam > *ewam > *wam > vam; wek *ebaj- > {{q|ku|vajî}}). Min gotibû qey di zarawayê kesekî da hebûye û wî bi ser va kiriye û jixwe nepêkan nîn e ku di zimên da hebe îcar rind e, nê min jî serkanî tine ji bonê. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 21:37, 23 nîsan 2025 (UTC)
:::https://bnk.institutkurde.org/images/pdf/4UVB2YMZG3.pdf
:::Di vê kitêbê de bi forma wam derbas dibe 1 Wam : qerz, dên. " dîsa jî gereg 3 çavkanî hebe.
:::Lê di beşa etîmolojiyê de eger em karibin zimanekî yekgirtî biemilînin, dê baştir be. Wekî din "bi vê enflasyona kelîmeyan" em nikarin ti tiştî bidin fêmkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:54, 23 nîsan 2025 (UTC)
== [[arvanê hûr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:48, 27 nîsan 2025 (UTC)
== [[damezrak]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:47, 27 nîsan 2025 (UTC)
== [[mezrak]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:49, 27 nîsan 2025 (UTC)
== [[hevhemetî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:25, 6 gulan 2025 (UTC)
== [[navhemetî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:25, 6 gulan 2025 (UTC)
== [[entîte]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:08, 17 gulan 2025 (UTC)
== [[agirvekujêner]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:35, 21 gulan 2025 (UTC)
== [[progres]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:50, 23 gulan 2025 (UTC)
== [[bûncî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:29, 28 gulan 2025 (UTC)
== [[tasît]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:36, 31 gulan 2025 (UTC)
== [[implîcît]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:37, 31 gulan 2025 (UTC)
== [[hewedar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:14, 31 gulan 2025 (UTC)
== [[destajo]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:38, 31 gulan 2025 (UTC)
== [[binhimbank]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:23, 1 hezîran 2025 (UTC)
== [[şîmane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:31, 2 hezîran 2025 (UTC)
== [[materya]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:12, 5 hezîran 2025 (UTC)
== [[bêtî]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:03, 7 hezîran 2025 (UTC)
== [[rîbat]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:21, 9 hezîran 2025 (UTC)
== [[hevtawan]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:26, 12 hezîran 2025 (UTC)
:Pirsek: "Hevwate" kelîmeyên ku maneyan wan hev in. "hevtawan" çawa dibe "şirîkên sûcekê"? Gereg sûcên ku tiştê wan hev bin? Ew tişt çi ye? Çima dibe şirîk? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:32, 12 hezîran 2025 (UTC)
::Merheba!
::"Hevtawan" anku "şirîkên heman tawanê, kesên ku heman tawan kiriye/dikin", wek "hevkar = şirîkên heman karî, kesên ku heman karî dikin", "hevjîn = şirîkên heman jiyanê, kesên ku bi hev re dijîn"... [[Bikarhêner:Ferhengvan|Ferhengvan]] ([[Gotûbêja bikarhêner:Ferhengvan|gotûbêj]]) 06:11, 30 hezîran 2025 (UTC)
== [[zanîş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:07, 14 hezîran 2025 (UTC)
== [[zanîşwer]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:07, 14 hezîran 2025 (UTC)
== [[zanmend]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:09, 14 hezîran 2025 (UTC)
== [[zandar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:08, 14 hezîran 2025 (UTC)
== [[zayîndar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:12, 14 hezîran 2025 (UTC)
== [[zayindar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:12, 14 hezîran 2025 (UTC)
== [[akund]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:16, 16 hezîran 2025 (UTC)
== [[destbikar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:41, 22 hezîran 2025 (UTC)
== [[vekitandin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:DarkAngel2100|DarkAngel2100]] ([[Gotûbêja bikarhêner:DarkAngel2100|gotûbêj]]) 10:45, 2 tîrmeh 2025 (UTC)
Çavkanî: Kurdish-English Dictionary by Baran Rizgar
== [[peşarmend]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:54, 5 tîrmeh 2025 (UTC)
== [[zaîd]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:59, 6 tîrmeh 2025 (UTC)
== [[Xrîst]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:13, 10 tîrmeh 2025 (UTC)
== [[hesibanî kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:48, 13 tîrmeh 2025 (UTC)
== [[top danîn]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:08, 13 tîrmeh 2025 (UTC)
== [[çûkger]] ==
@[[Bikarhêner:Dûrzan|Dûrzan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:49, 18 tîrmeh 2025 (UTC)
== [[dûneçkar]] ==
@[[Bikarhêner:212.175.166.246|212.175.166.246]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:38, 10 tebax 2025 (UTC)
== [[sermirov]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:22, 13 tebax 2025 (UTC)
== [[nifira Esed]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:00, 19 tebax 2025 (UTC)
== [[dilkrîz]] ==
@[[Bikarhêner:Kurd87612|Kurd87612]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:44, 23 tebax 2025 (UTC)
== [[berxa Xwedê]] ==
@[[Bikarhêner:Kurd87612|Kurd87612]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:28, 31 tebax 2025 (UTC)
== [[destmatî]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:47, 21 îlon 2025 (UTC)
== [[sîbaq]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:33, 27 îlon 2025 (UTC)
== [[mirovê daristanê]] ==
@[[Bikarhêner:Hewarwîkîferheng|Hewarwîkîferheng]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:09, 18 çiriya pêşîn 2025 (UTC)
== [[çavbijîşk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:30, 24 çiriya pêşîn 2025 (UTC)
== [[makeşar]] ==
@[[Bikarhêner:Avestaboy|Avestaboy]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:46, 14 kanûna pêşîn 2025 (UTC)
:Silav @[[Bikarhêner:Bikarhêner|Bikarhêner]] min bi derew peyv çê nekir. Peyva makeşar di sepana O-ferheng û her wiha di ferhenga online ferhengkurdi.org ya '''Enstîtuya Kurdî ya Stenbolê''' tê xwendin.
:https://ferhengkurdi.org/tr/kelime/make%C5%9Far-n [[Bikarhêner:Avestaboy|Avestaboy]] ([[Gotûbêja bikarhêner:Avestaboy|gotûbêj]]) 11:07, 18 kanûna pêşîn 2025 (UTC)
::Krîterên me yên tevlîkirina kelîmeyan heye. Ev made rîayeta wan krîteran nake. Lewma min vê kelîmeyê li vir bicih kir. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:28, 18 kanûna pêşîn 2025 (UTC)
== [[bihîstinzanî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:30, 23 hezîran 2026 (UTC)
== [[hevdilî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:28, 24 hezîran 2026 (UTC)
== [[kemisandin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:45, 24 hezîran 2026 (UTC)
== [[kexme]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:53, 24 hezîran 2026 (UTC)
== [[lêpirsyarker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:32, 24 hezîran 2026 (UTC)
== [[raypirs]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:34, 24 hezîran 2026 (UTC)
== [[helwestpirs]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:34, 24 hezîran 2026 (UTC)
== [[kopirî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' xuyaye maneyeke wekî pir jî heye dikare were zêdekirin —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:33, 25 hezîran 2026 (UTC)
== [[merxeme]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:55, 25 hezîran 2026 (UTC)
== [[mohl]] ==
@[[Bikarhêner:Far ati|Far ati]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:14, 25 hezîran 2026 (UTC)
== [[irfandarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:30, 26 hezîran 2026 (UTC)
== [[axulme]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:20, 30 hezîran 2026 (UTC)
== [[şewane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:26, 1 tîrmeh 2026 (UTC)
== [[poezî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:27, 1 tîrmeh 2026 (UTC)
== [[sebaxker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:20, 1 tîrmeh 2026 (UTC)
== [[sebaxfiroş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:21, 1 tîrmeh 2026 (UTC)
== [[siboxfiroş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:21, 1 tîrmeh 2026 (UTC)
== [[diyardenasîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:26, 1 tîrmeh 2026 (UTC)
== [[dawxane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:41, 3 tîrmeh 2026 (UTC)
== [[pêşajotî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:15, 3 tîrmeh 2026 (UTC)
== [[kontînentaltirîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:35, 3 tîrmeh 2026 (UTC)
== [[azmayîşker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:16, 3 tîrmeh 2026 (UTC)
== [[ateşparêz]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 05:19, 4 tîrmeh 2026 (UTC)
== [[kurtetarîx]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:52, 4 tîrmeh 2026 (UTC)
== [[besayetî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:57, 4 tîrmeh 2026 (UTC)
== [[tawbijêr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:45, 4 tîrmeh 2026 (UTC)
== [[destbir]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:30, 5 tîrmeh 2026 (UTC)
== [[nekider]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:56, 5 tîrmeh 2026 (UTC)
== [[niqar]] ==
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:39, 5 tîrmeh 2026 (UTC)
== [[parêzîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ev form li ti derê peyda nabe. Berî çendîn salan hatîye çêkirin. Bi qenaeta min şaş e, lewma em jê bibin. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:29, 6 tîrmeh 2026 (UTC)
== [[stêrfizîk]] ==
. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:33, 6 tîrmeh 2026 (UTC)
== [[pêçavik]] ==
. Eger ev kelîmeyeke mehelî be, Jkx şablona {{şx|dever|cihê ku lê tê bikaranîn}} li madeyê zêde bike. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:59, 7 tîrmeh 2026 (UTC)
== [[qerqef]] ==
. Eger ev kelîmeyeke mehelî be, Jkx şablona {{şx|dever|cihê ku lê tê bikaranîn}} li madeyê zêde bike. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:17, 7 tîrmeh 2026 (UTC)
== [[rakar]] ==
. Eger ev kelîmeyeke mehelî be, Jkx şablona {{şx|dever|cihê ku lê tê bikaranîn}} li madeyê zêde bike. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:30, 7 tîrmeh 2026 (UTC)
== [[remilîn]] ==
. Eger ev kelîmeyeke mehelî be, Jkx şablona {{şx|dever|cihê ku lê tê bikaranîn}} li madeyê zêde bike. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:36, 7 tîrmeh 2026 (UTC)
== [[segurî]] ==
. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:51, 7 tîrmeh 2026 (UTC)
== [[serdev]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:10, 7 tîrmeh 2026 (UTC)
== [[serziman]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:01, 7 tîrmeh 2026 (UTC)
== [[tenîsk]] ==
di Înternetê de gelek netîce derdikevin lê ji bo ku dûre ez kontrol bikim û çavkaniyên sehîh binivîsim li vir îlawe dikim. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:53, 7 tîrmeh 2026 (UTC)
== [[tertî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:58, 7 tîrmeh 2026 (UTC)
== [[tirut]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:25, 7 tîrmeh 2026 (UTC)
== [[tizqa]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:28, 7 tîrmeh 2026 (UTC)
== [[têkilîdêr]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:31, 7 tîrmeh 2026 (UTC)
== [[tîtalk]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:33, 7 tîrmeh 2026 (UTC)
== [[vircoq]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:45, 7 tîrmeh 2026 (UTC)
== [[xilêbat]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]]. Li gorî kurteya çêkirina sayfeyê li Qamişlo û Amûdê tê bikaranîn. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:14, 7 tîrmeh 2026 (UTC)
== [[xin_û_cûc]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:27, 7 tîrmeh 2026 (UTC)
== [[çiteba]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:38, 7 tîrmeh 2026 (UTC)
== [[desttêl]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 08:45, 8 tîrmeh 2026 (UTC)
== [[Mîmedol]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 08:51, 8 tîrmeh 2026 (UTC)
== [[alaf]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:01, 8 tîrmeh 2026 (UTC)
== [[aşirme]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:13, 10 tîrmeh 2026 (UTC)
== [[geledîmentî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:54, 13 tîrmeh 2026 (UTC)
== [[çendîmentî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:54, 13 tîrmeh 2026 (UTC)
== [[rojeng]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:41, 18 tîrmeh 2026 (UTC)
== [[bereyîşk]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:47, 19 tîrmeh 2026 (UTC)
== [[wesekname]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:08, 22 tîrmeh 2026 (UTC)
== [[cemaetgeh]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:17, 24 tîrmeh 2026 (UTC)
== [[iblîskanî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:27, 30 tîrmeh 2026 (UTC)
== [[pehnik]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:51, 4 tebax 2026 (UTC)
:# Kamiran bedirxan û H. Muhammed bi maneya guwerte bi kar tînin.
:# Navê Çiyayek e li Hawarê derbas dibe
:# Farqînî tenê dibêje "yassı"
:#* [https://usercontent.one/wp/portal.arsivakurd.org/wp-content/uploads/2021/08/Azadiya-welat_67.pdf?media=1709107483 Azadiya welat hj 67] Roşan Lezgîn kevirên pehnik
:# Ferhenga destî pahnik pehnik dibêje
:# Rojen barnas li vir bi çi mana bi kar tîne?
:#* {{jêder-kitêb|ku|jêgirtin=... '''pehnika''' dirandar tê kişandin û ji nû ve bi mîxên têlkî ve tê girêdan ku firk û sistî nekevê. Piştî ku dirûtina rû bi binpiyê hundirîn ve xelas bû vêca dora binpiyê derva (binî) tê. Binpiyê binî li ser yê hun- dirîn an bitenê bi...|sal=2013|nivîskar=Rojen Barnas, Nûbihar|url=http://books.google.com.tr/books?id=pVDKEAAAQBAJ&pg=PA67&dq=%22wan+pehnikan%22+OR+%22pehnik%C3%AA%22+OR+%22pehnikin%22+OR+%22pehnikek%C3%AA%22+OR+%22pehnika%22+OR+%22w%C3%AA+pehnik%C3%AA%22+OR+%22pehnikino%22+OR+%22pehnikine%22&hl=&cd=4&source=gbs_api|sernav=Sîlvan/ Silîvan / Farqîn|weşanxane=PAK AJANS YAYINCILIK TURİZM VE DIŞ TİCARET LİMİTED ŞİRKETİ|ISBN=9786055402884|rûpel=67}}
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:40, 6 tebax 2026 (UTC)
== [[temirîndox]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:56, 6 tebax 2026 (UTC)
== [[kansêrolojî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:17, 6 tebax 2026 (UTC)
== [[navçeparêzî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:09, 6 tebax 2026 (UTC)
== [[goganîbûnî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:23, 15 tebax 2026 (UTC)
== [[zewijandin]] ==
Bêyî çavkaniyan e. --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 18:34, 22 tebax 2026 (UTC)
== [[karbes]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 12:42, 29 tebax 2026 (UTC)
== [[biehliyet]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:41, 31 tebax 2026 (UTC)
== [[pofêldang]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 07:09, 10 îlon 2026 (UTC)
== [[jenk]] ==
Ji xeynî Farqînî ti çavkanî nîne. Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:29, 19 îlon 2026 (UTC)
== [[bedewnivîs]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:20, 20 îlon 2026 (UTC)
l1p39isztlz8wbjuge03vghx2u6ag9i
6534986
6534985
2026-09-20T19:21:14Z
Wikihez
7344
[[Bikarhêner:Wikihez/skrîpt/js/şablon-lêke.js|T]]: Daxwaza peyitandinê: +[[spehînivîs]]
6534986
wikitext
text/x-wiki
{{/destpêk}}
= Hezîran 2020 =
== [[romî#Navdêr|romî]] ==
Li Şingalê maneya polîs? --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:00, 15 hezîran 2020 (UTC)
= Tîrmeh 2020 =
== [[kêlb#p-mane|kêlb]] ==
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:24, 22 tîrmeh 2020 (UTC)
== [[ejî#p-mane|ejî]] ==
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:24, 22 tîrmeh 2020 (UTC)
== [[tutirk#peyitandin|tutirk]] ==
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:02, 22 tîrmeh 2020 (UTC)
:{{r|Guherto}} Niha çawa ye? Bi ya min çavkaniyên ku min dane têrê me dike. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:43, 22 tîrmeh 2020 (UTC)
::{{r|Balyozxane}} Nizanim.. li hin deveran wek tutirk hatîye nivîsandin û li hin jî wek tûtirk. Li hin deveran bi wateya tûşêmî (Rubus) ye, hin deveran jî bi wateya tûerd (Fragaria × ananassa) e û carna jî bi wateya tû (ê spî û reş go li ser daran çê dibin) ye. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 08:16, 23 tîrmeh 2020 (UTC)
== [[erbane#p-mane|erbane]] ==
Ji bo ''Amûreke tenê tekerekî wê heye ji bo veguhestina tiştan wek: axê, kevir, hwd'' ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]''--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:42, 31 tîrmeh 2020 (UTC)
= Tebax 2020 =
== [[cifta berazan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:57, 9 tebax 2020 (UTC)
== [[tê gerîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:26, 10 tebax 2020 (UTC)
== [[tapor]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:30, 22 tebax 2020 (UTC)
:{{r|Mihemed Qers}} -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 22:16, 22 tebax 2020 (UTC)
== [[şimzare]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:01, 26 tebax 2020 (UTC)
== [[bîrkok]] ==
Tenê Mehmed Uzun bi kar tîne. Ji bilî kitêbên wî çavkaniyeke din tune. —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:38, 28 tebax 2020 (UTC)
== [[hunera axaftinê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:46, 30 tebax 2020 (UTC)
== [[derrijîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 07:38, 31 tebax 2020 (UTC)
== [[yekal]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 09:19, 31 tebax 2020 (UTC)
== [[av û agir]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:50, 31 tebax 2020 (UTC)
: Di çavkaniyeke de wek av û agir kirin derbas dibe û ji wergerandinên inglîzî cuda ye lê nêzîk e.<br> {{jêder-pirtûk|ku|jêgirtin=weke gelek hunermendên ko dû re agir bera berhêm xwe didin Xwedê jî ji bilî maltaba Nûh tevayiyê vê berhema xwe '''av û agir dike''', van melekên xwe yên doxînsist dikişîne asîmanan, li wir wan tawanbar dike, mîna ko bibêje: de biçin û weke xwe bikin, bi derew û tevgerên xwe yên qirêj dijminiya min bikin, Xwedê wan ji asimanan de serjêr davêje xwarê.|nivîskar=Hesenê Metê|sernav=Labîrenta cinan|weşanxane=Nefel|sal=2002|rûpel=132}} --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:37, 31 tebax 2020 (UTC)
== [[elm#p-mane-notice|elm]] ==
Wateya: ''[[ewlebûn]], sihê pê zanîn, baş pê zanîn''. ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:31, 31 tebax 2020 (UTC)
:{{r|Hakîmo}} Ku çavkanî tunebûn em ê jê bibin. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:47, 19 îlon 2020 (UTC)
::{{r|Balyozxane}} Mala te neketê... Hingê bixwe here lê bigere. Jixwe ez reşwî me. Ma eve ne çavkanî ye? -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 14:48, 19 îlon 2020 (UTC)
:::{{r|Hakîmo}} Hinek bixwîne [[Wîkîferheng:Pîvekên tevlîkirinê]]. Jixwe dema ti kesan din çavkanî lê zêde nakin, ez lê digerim û zêde dikim. Na tu ne ti çavkanî yî. Çavkaniyên nivîskî hewce ne. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:51, 19 îlon 2020 (UTC)
::::{{r|Balyozxane}} Erê, lê ku tine bin em çawa bikinî? Em hema jê bibinî? -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 14:52, 19 îlon 2020 (UTC)
:::::{{r|Hakîmo}} Erê em jê dibin. Lê peyvên jê hatine birin em li rûpela [[Pêvek:Bêçavkanî]] zêde dikin. bnr. [[:Kategorî:Pêvek bêçavkanî]]. Dîsa jî ez jî ji vê rewşa me aciz im û bi ya min peyvên devokî divê nayên jêbirin. Lê ji ber ku [[Wîkîferheng:Rêbazname]] dibêje divê werin jêbirin, ez jê dibim. Hevalên {{r|Jiju|Guherto|Ghybu}} [https://ku.wiktionary.org/wiki/Got%C3%BBb%C3%AAj:kejm%C3%BBn], [https://ku.wiktionary.org/wiki/Got%C3%BBb%C3%AAja_p%C3%AAvek%C3%AA:B%C3%AA%C3%A7avkan%C3%AE] jî ji vê rewşê aciz in. Lê ti kes gotûbêj nadin destpêkirin. Ku tu li vir [[Gotûbêja Wîkîferhengê:Pîvekên tevlîkirinê]] gotûbêjek bidî destpêkirin, ez ê piştgirî bidime te. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:58, 19 îlon 2020 (UTC)
::::::Ji kerema xwe min şaş fêm mekin feqet hemû bikarhêner ku tên vira dilpak nînin. Em çito ew peyvan kotrol bikinê? Mixabin, ji bo konrolkirina peyvan di destên me de tenê xwastina çavkaniyan hene amûrek din tine ye... PS: Herwiha me li ser wîkîferhengê Namespace'yên Nimînok ([[Nimînok:Kurdî/serhedî]]?, [[Nimînok:Kurdî/badînî]]?, ..) û Portal ([[Portal:Kurdî]], [[Portal:Frensî]] (mk. [[:fr:Portail:Français]]), ...) jî çêkiriye...--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:32, 19 îlon 2020 (UTC)
:::::::Nizanim we nivîsa vira xwand, [[Wîkîferheng:Dîwan#Skotî]]. Piraniya peyvên wîkiya skotî çêkirî ne...--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:47, 19 îlon 2020 (UTC)
::::::::@Ghybu erê min ew xwendibû. Ku em peyvan nepeyitînin, rewşa me jî wek mg.wikt û wikiya skotiyan be. Temam em peyvên devokî jê nebin lê em wan jî nexin werger û rûpelên din heta ku werin peyitandin. Herwiha tu dibêjî amûrek tune ye lê tenê ji bo peyvên devokî û navên heywan/riwekan wisa ye. Gelek caran peyitandin pir hêsan e. Mînak @Hakîmo yan jî hevalên din dikarin blogan binivîsin daku em karibin peyvên wan yên devokî (nepeyitandî) têxin rûpela [[Wîkîferheng:Tomarkirina peyvên ji wêjeya kurdî|Jêgirtinekê tomar bike]] û kengî ew peyv ji hêla nivîskar û blogvanên kurd yên din ve hatine bikaranîn hingê em dikarin li wîkîferhengê jî zêde bikin. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:05, 19 îlon 2020 (UTC)
{{outdent|::::::::}}{{r|Hakîmo|Ghybu}} Xêra dê û bavên we '''[[Gotûbêja_Wîkîferhengê:Pîvekên_tevlîkirinê#Li_ser_peyvên_devokî|li vir]]''' fikrên xwe binivîsin daku kesên din bizanin kesên din jî hene vê fikrê min dipejirînin. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 06:48, 3 çiriya pêşîn 2020 (UTC)
== [[nîşanbar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:51, 31 tebax 2020 (UTC)
= Îlon 2020 =
== [[besme]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:26, 1 îlon 2020 (UTC)
== [[ferengi]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:12, 1 îlon 2020 (UTC)
== [[çelape]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:22, 1 îlon 2020 (UTC)
== [[cesasandin]] ==
Çavkaniyek klasîk heye, yên din heye gelo? —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 09:02, 2 îlon 2020 (UTC)
== [[depan]] ==
Depan guhertoyeke textan an textîn e. Lê li vir çi mane heye min fêm nekir. —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:33, 2 îlon 2020 (UTC)
== [[li rûyê dinê xweş mayîn]] ==
Çavkaniyeke klasîk heye —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:38, 2 îlon 2020 (UTC)
== [[bêsewî]] ==
klasîk —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 06:34, 4 îlon 2020 (UTC)
== [[merkemoş]] ==
Klasîk —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 06:34, 4 îlon 2020 (UTC)
== [[firfiroka bayî]] ==
{{r|Mamoste Dilovan}} ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:57, 6 îlon 2020 (UTC)
== [[guhê fîlî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:56, 7 îlon 2020 (UTC)
: {{r|Bikarhêner}} Min guh li evê peyvê bû ji kesekî ku xudan xwaringehek û baxçeyekî mezin e.
: Gelek tişt di berbelav in û xelk dizanin, lê di pertûkan de naye dîtin. {{bêîmze|IbrahimKocher Duhok}}
== [[gurovî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:41, 14 îlon 2020 (UTC)
:{{r|Bikarhêner}} Çavkanîyên peyvên ku min nivîsandin mixabin ne nivîskî ne. Ev peyv min ji hin mezinên malbatê bihîstine û min jî gelek caran dîtîne ku hatine bikaranîn. Ji bo ku ev peyv wûnda nebin min hem li ba xwe û hem jî li wîkîferhengê nivîsand. Ji bo peyvên ku çavkanîyên nivîskî tunin, mixabin gotinên devkî tene weke çavkanî dimînin. Pêşnîyara te ji bo pevên bi vî awayî çi ye gelo? Spas{{bêîmze|Xwende}}
::{{r|Xwende}} Pêşî li ser malpera [https://tirsik.net/xerhati.php] dûre jî li ser arsiv.org qeyd bike ku nikarî çavkanî peyda bikî. Herwiha bnr [[Alîkarî:Îmze]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:00, 15 îlon 2020 (UTC)
== [[şîloyî]] ==
{{r|Xwende}} ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:44, 14 îlon 2020 (UTC)
== [[dever]] ==
Ji bo wateya ''derence, sêlim û merdîwen'' [[Wîkîferheng:Pîvekên tevlîkirinê|çavkanî hewce ye]]. —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:53, 14 îlon 2020 (UTC)
== [[kerrnizî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:56, 15 îlon 2020 (UTC)
== [[li dora (yekê) gerîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:46, 15 îlon 2020 (UTC)
== [[glas]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]. --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 12:45, 16 îlon 2020 (UTC)
== [[kejmûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:42, 17 îlon 2020 (UTC)
== [[lirêgerîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:20, 18 îlon 2020 (UTC)
== [[çavpeqî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:23, 23 îlon 2020 (UTC)
:http://www.geocities.ws/mehname2004a/63/pexsan5.html -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 16:58, 23 îlon 2020 (UTC)
== [[çavpeqiyayî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:14, 23 îlon 2020 (UTC)
:{{r|Balyozxane}} Çavpeqî û -iyayî eynî peyv in, heke bo nimûne 2 çavkanî ji bo çavpeqî û 1 ji bo -iyayî werin dîtin ew weke 3 ji bo yek peyv tên jimartin? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:16, 23 îlon 2020 (UTC)
::{{r|Guherto}}. Na, divê yek ji wan peyitandî be daku em karibin yek ji wan wek guherto nîşan bidin. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:45, 23 îlon 2020 (UTC)
:::Temam naxwe. {{=(}} -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:48, 23 îlon 2020 (UTC)
::::Ecêb e peyva çavpeqîn jî tune ye. Divê çavpeqî/çavpeqiyayî jê hatibe lê qet tune. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:56, 23 îlon 2020 (UTC)
:::::Em çavpeqî ne weke kesekî ku çavê wan peqî ye dibêjin. Siqêfek e. ''Wilo neke, çavpeqî!'' -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 18:10, 23 îlon 2020 (UTC)
== [[olfatûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:58, 24 îlon 2020 (UTC)
:Du çavkanî hatiye dîtin li hêviya ya sisêyan e. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:46, 23 sibat 2025 (UTC)
== [[hekûke]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:00, 28 îlon 2020 (UTC)
:Tenê çavkaniyek ji Kovara Kurmancî heye ew jî ji kitêba Gundê Mêrxasan a Eliyê Evdilrehman hatiye girtin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:49, 23 sibat 2025 (UTC)
== [[dê bêjî kaya di serî de]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:58, 29 îlon 2020 (UTC)
== [[heçî ne li malekê, nizanit halekê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:58, 29 îlon 2020 (UTC)
== [[heta nîva destî reş nebit, tama devî xoş nabit]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:58, 29 îlon 2020 (UTC)
== [[kirtop]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Herwiha [[kirtope]]—[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:59, 29 îlon 2020 (UTC)
:
== [[pişkûmişk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:25, 29 îlon 2020 (UTC)
= Çiriya pêşîn 2020 =
== [[ma te şulê pê ye?]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:19, 1 çiriya pêşîn 2020 (UTC)
== [[zelam yêt heyn û zelamoşk jî yêt heyn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:28, 1 çiriya pêşîn 2020 (UTC)
== [[ezîz î; ji pêrve ne mîzî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:15, 1 çiriya pêşîn 2020 (UTC)
== [[qelpez]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:19, 6 çiriya pêşîn 2020 (UTC)
== [[firêzer]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:27, 6 çiriya pêşîn 2020 (UTC)
== [[kirmişk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 09:03, 7 çiriya pêşîn 2020 (UTC)
== [[orijîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:03, 7 çiriya pêşîn 2020 (UTC)
== [[şequdeq]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:50, 9 çiriya pêşîn 2020 (UTC)
== [[helizandin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:23, 9 çiriya pêşîn 2020 (UTC)
== [[rewastin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:48, 9 çiriya pêşîn 2020 (UTC)
== [[xunaftin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:31, 9 çiriya pêşîn 2020 (UTC)
== [[itîmad li ser kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 16:32, 15 çiriya pêşîn 2020 (UTC)
== [[sengesor]] ==
{{r|Saberabdollahizad}} ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:00, 16 çiriya pêşîn 2020 (UTC)
:Navçeya Mirgewerê, Ûrmîye. {{bêîmze|Saberabdollahizad}}
== [[zenek]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:03, 16 çiriya pêşîn 2020 (UTC)
:{{r|Saberabdollahizad}} [[zenek]] jî li Ûrmiyê tê bikaranîn? --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:53, 18 çiriya pêşîn 2020 (UTC)
== [[pind#p-mane|pind]] ==
Min tenê çavkaniyek dît ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:48, 17 çiriya pêşîn 2020 (UTC)
:Navçeya Mirgewerê, Ûrmîye. {{bêîmze|Saberabdollahizad}}
== [[maqezer]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:53, 18 çiriya pêşîn 2020 (UTC)
:Navçeya Mirgewerê, Ûrmîye. {{bêîmze|Saberabdollahizad}}
== [[pezpezok]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:55, 18 çiriya pêşîn 2020 (UTC)
:Navçeya Mirgewerê, Ûrmîye. {{bêîmze|Saberabdollahizad}}
== [[parapet#p-mane|parapet]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:53, 31 çiriya pêşîn 2020 (UTC)
= Çiriya paşîn 2020 =
== [[quloç#p-mane|quloç]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:43, 19 çiriya paşîn 2020 (UTC)
== [[di ber (yekî) de avêtin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:57, 20 çiriya paşîn 2020 (UTC)
:https://tirsik.net/index.php?mijarid=18749&iro=on&rupel=1 —[[Bikarhêner:Mamoste Dilovan|Mamoste Dilovan]] ([[Gotûbêja bikarhêner:Mamoste Dilovan|gotûbêj]]) 17:37, 20 çiriya paşîn 2020 (UTC)
:: {{r|Mamoste Dilovan}} [[di ber re avêtin]] heye. lê te wek di ber '''de''' lê zêde kir. Em biguherînin? Gelek çavkanî ji bo di ber re avêtin heye. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:41, 20 çiriya paşîn 2020 (UTC)
== [[hêvastin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:49, 21 çiriya paşîn 2020 (UTC)
== [[lamçîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:28, 22 çiriya paşîn 2020 (UTC)
== [[poz#p-mane|poz]] ==
Ji bo maneya "[[quz]], [[vajîna]]" ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:28, 22 çiriya paşîn 2020 (UTC)
== [[sihetbexş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:35, 24 çiriya paşîn 2020 (UTC)
== [[hevjêhatin ji jêhatîbûnê çêtir e]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:14, 25 çiriya paşîn 2020 (UTC)
== [[gerriyan#p-mane|gerriyan]] ==
''Ji bo wateya cereyan (tevger)''. Qasî ku dibînim tenê Cewerî bi kar aniye —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:24, 25 çiriya paşîn 2020 (UTC)
== [[derbend]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:08, 30 çiriya paşîn 2020 (UTC)
= Kanûna paşîn 2021 =
== [[desthelgirtin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:48, 6 kanûna paşîn 2021 (UTC)
== [[êşewî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:52, 7 kanûna paşîn 2021 (UTC)
== [[sexorr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:22, 7 kanûna paşîn 2021 (UTC)
== [[sapût]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:06, 12 kanûna paşîn 2021 (UTC)
= Çiriya pêşîn 2022 =
== [[paşbingeh]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:40, 12 çiriya pêşîn 2022 (UTC)
= Kanûna paşîn 2023 =
== [[berê beringa]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:35, 1 kanûna paşîn 2023 (UTC)
== [[mûlûleya xwînê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:36, 6 kanûna paşîn 2023 (UTC)
== [[mirovdostî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 05:38, 12 kanûna paşîn 2023 (UTC)
== [[derîner]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:02, 15 kanûna paşîn 2023 (UTC)
== [[neteba]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:01, 16 kanûna paşîn 2023 (UTC)
:Maneya "uyruksuz" ji ferhenga Farqînî hatiye kopîkirin wekî din bi giştî bi maneya "nakokî" tê bikaranîn. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:33, 26 hezîran 2026 (UTC)
== [[narkotîkfiroş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:59, 16 kanûna paşîn 2023 (UTC)
== [[pirik#p-mane|pirik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:15, 16 kanûna paşîn 2023 (UTC)
== [[ramyarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ez dibêjim ev peyv tenê bi maneya "fikrî", wek rengdêr heye. Lê belê di soranî de bi şaşemilandineke wekî "siyasî" bi kar anîne, kurmancên me jî rasterast maneya soranî tevlî çend ferhengên hev-kopîkirî kirine û bi vî awayî belkî hin çavkanî hebin. Lê ez dibêjim divê ew çavkanî jî neyên hesibandin. Hin kes zen dikin ku her tiştê di soranî de ripîrast e. Mesele heqeten eletewş e.
Agahiyeke li ser peyvê: "[https://www.welateme.info/kurdi/modules.php?name=News&file=print&sid=686 Welateme.info]"
Li vir peyv bi maneya "fikrî" hatiye bikaranîn: "[https://xwebun1.org/ziman-sirhelal-e/ Xwebûn]". Qewet be. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:36, 19 kanûna paşîn 2023 (UTC)
== [[dolîwgeh]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:08, 22 kanûna paşîn 2023 (UTC)
= Sibat 2023 =
== [[herebaş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:13, 11 sibat 2023 (UTC)
== [[balindeyê korare]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].''
Tenê çavkaniyekê tê xuyan, ew jî kitêba "Balindeyên Kurdistanê" yê Îsmaîl Taha Şahîn e (Îsmaîl Taha Şahîn (2006). Balindeyên Kurdistanê, 1-4. Spîrêz, Duhok). Ev kitêb ji ser înternetê wek word jî peyda dibe. Xuya ye ku ji Weşanên Spîrez fîzîken jî çap bûye. Lê wekî çavkanî tenê ev tê xuyan.
—[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:10, 14 sibat 2023 (UTC)
== [[dîbeyt]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:13, 18 sibat 2023 (UTC)
== [[çend pat]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:35, 24 sibat 2023 (UTC)
== [[rabînîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ji bo kurmancî bêçavkanî ye. Ger ji bo soranî çavkanî hebin jî divê ji bo soranî bi herfên aramî were nivîsîn.
An ger halê latînîkirî ya peyveke soranî be, dîsa divê ev hal were eşkerekirin. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:36, 26 sibat 2023 (UTC)
= Adar 2023 =
== [[qirar#p-mane|qirar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:23, 2 adar 2023 (UTC)
== [[pîşpêkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Tenê di nivîseke online ya Cemal Nebez de derbas dibe. Dibe ku peyveke başûriyan be. Û di wê nivîsa Cemal Nebezê bi forma "pîş bi ... bikin" jî derbas dibe. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:29, 3 adar 2023 (UTC)
== [[kûcag]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:27, 27 adar 2023 (UTC)
== [[destbizavî kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].''
Xuya ye ku ev peyv tenê di kitêba “''Nameyek Şernexê''” yê Evdirehman Gundikî de derbas bûye. Û ji wir jî Mistefa Aydoganî maneya peyvê di hejmara 61em ya Kovara ''Kurmancî'' de belav kiriye (rûpela 2yem). Lê belê tenê yek çavkaniyekê ne bes e ji bo ku em vê peyvê di vê ferhengê de wekî peyveke çavkanî-temam belav bikin. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:40, 28 adar 2023 (UTC)
== [[germav baş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:23, 31 adar 2023 (UTC)
== [[germavxweş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:27, 31 adar 2023 (UTC)
= Gulan 2023 =
== [[kirbend]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:34, 5 gulan 2023 (UTC)
: Çêkerê rûpelê wek not nivîsiye ku peyv ji herêma Çewlikê ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:24, 6 gulan 2023 (UTC)
== [[We are off now]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:13, 22 gulan 2023 (UTC)
== [[baran ji erd û esmana lê diket]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:46, 26 gulan 2023 (UTC)
== [[sîba]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:44, 30 gulan 2023 (UTC)
== [[bîrîner]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:20, 30 gulan 2023 (UTC)
= Hezîran 2023 =
== [[bidestmiz]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:49, 10 hezîran 2023 (UTC)
= Tîrmeh 2023 =
== [[parzeng]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:01, 15 tîrmeh 2023 (UTC)
== [[zerpedest]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:05, 15 tîrmeh 2023 (UTC)
== [[heta destên mirov nebine gû, devê mirov nabite tû]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:27, 19 tîrmeh 2023 (UTC)
== [[çemê mezin tê dibore diçe]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:38, 19 tîrmeh 2023 (UTC)
== [[kevanê nebî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:45, 19 tîrmeh 2023 (UTC)
== [[teybûr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Tenê li malpera yuksekovahaber.com-ê derbas dibe, li wir jî bi maneya "çare, çareserî"yê hatiye emilandin, ne bi vê maneyê. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:00, 19 tîrmeh 2023 (UTC)
== [[nikêtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:22, 19 tîrmeh 2023 (UTC)
= Tebax 2023 =
== [[prêze]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:19, 15 tebax 2023 (UTC)
== [[qelye]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:59, 25 tebax 2023 (UTC)
= Îlon 2023 =
== [[hetwan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:21, 14 îlon 2023 (UTC)
== [[milyonmasî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:06, 21 îlon 2023 (UTC)
== [[tofana mêjî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:20, 21 îlon 2023 (UTC)
== [[îshaf]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:57, 23 îlon 2023 (UTC)
== [[odeya karkirinê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:57, 26 îlon 2023 (UTC)
== [[odeya şixulînê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:42, 26 îlon 2023 (UTC)
== [[hîter]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:08, 26 îlon 2023 (UTC)
= Çiriya pêşîn 2023 =
== [[vîtamîn waw]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:20, 1 çiriya pêşîn 2023 (UTC)
== [[berhan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:21, 3 çiriya pêşîn 2023 (UTC)
== [[sêlikdêr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:16, 10 çiriya pêşîn 2023 (UTC)
== [[qewandêr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:20, 10 çiriya pêşîn 2023 (UTC)
== [[darav]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:04, 11 çiriya pêşîn 2023 (UTC)
= Çiriya paşîn 2023 =
== [[kansernasî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:54, 14 çiriya paşîn 2023 (UTC)
== [[golgir]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:06, 15 çiriya paşîn 2023 (UTC)
== [[ahardar]] ==
Tenê Zana Farqînî —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:34, 26 çiriya paşîn 2023 (UTC)
= Kanûna pêşîn 2023 =
== [[Kî mal ezîz bike, can rezîl dike; kî mal rezîl bike, can ezîz dike.]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:55, 2 kanûna pêşîn 2023 (UTC)
== [[porsing]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:22, 2 kanûna pêşîn 2023 (UTC)
== [[newelatî kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:57, 2 kanûna pêşîn 2023 (UTC)
== [[newelatîkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:58, 2 kanûna pêşîn 2023 (UTC)
== [[alîkar kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 18:42, 3 kanûna pêşîn 2023 (UTC)
== [[aramoş kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'' -- [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 18:48, 3 kanûna pêşîn 2023 (UTC)
== [[bêvil kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 18:56, 4 kanûna pêşîn 2023 (UTC)
== [[dijberiya naverokî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 22:43, 8 kanûna pêşîn 2023 (UTC)
== [[bersivîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]]'', û her wiha ne li gorî qayideyên kurdî (kurmancî) ye. —[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 22:08, 11 kanûna pêşîn 2023 (UTC)
== [[hevalzewac]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:25, 18 kanûna pêşîn 2023 (UTC)
= Kanûna paşîn 2024 =
== [[raperk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:40, 15 kanûna paşîn 2024 (UTC)
:Bi wê manê nîne. Bi maneke dî heye. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:26, 6 tebax 2024 (UTC)
= Sibat 2024 =
== [[bi navê lîstin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:02, 3 sibat 2024 (UTC)
: Li ser agahdarkirina çêkerê rûpelê me şablona "dever"ê tevlî kir. Peyv ji herêma Qoser, Mêrdînê ye. Me li qismê "Bikaranîn"ê jî vê yekê dîyar kir. Ji xeynî vê me wê peyvê ji rûpelên din jî bir. Bi vî awayî ev made kare were girtin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:28, 10 nîsan 2024 (UTC)
== [[preskirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:43, 11 sibat 2024 (UTC)
== [[pres kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:43, 11 sibat 2024 (UTC)
== [[Xwedê te têr bike]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:41, 13 sibat 2024 (UTC)
== [[isad]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:09, 22 sibat 2024 (UTC)
== [[razemendîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:44, 27 sibat 2024 (UTC)
= Adar 2024 =
== [[zilhêzî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:19, 5 adar 2024 (UTC)
== [[hingihîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:32, 13 adar 2024 (UTC)
== [[xore]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:30, 15 adar 2024 (UTC)
== [[melef]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:55, 19 adar 2024 (UTC)
== [[uğursuz ayak]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:11, 23 adar 2024 (UTC)
== [[şepûk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:22, 23 adar 2024 (UTC)
== [[agirberdêrî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:04, 26 adar 2024 (UTC)
== [[kîrquz]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:00, 26 adar 2024 (UTC)
== [[kîrquzdê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:01, 26 adar 2024 (UTC)
== [[quzgêj]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:03, 26 adar 2024 (UTC)
= Nîsan 2024 =
== [[hîkarîkerî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:53, 1 nîsan 2024 (UTC)
== [[hîkarîker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:54, 1 nîsan 2024 (UTC)
== [[hîkarîdarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:55, 1 nîsan 2024 (UTC)
== [[hîkarîdar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:56, 1 nîsan 2024 (UTC)
== [[bihîkarîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:57, 1 nîsan 2024 (UTC)
== [[bihîkarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:58, 1 nîsan 2024 (UTC)
== [[bêhîkarîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:58, 1 nîsan 2024 (UTC)
== [[bêhîkarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:59, 1 nîsan 2024 (UTC)
== [[bêhîkarî bûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:00, 1 nîsan 2024 (UTC)
== [[bêhîkarî kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:00, 1 nîsan 2024 (UTC)
== [[bêhîkarîbûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:01, 1 nîsan 2024 (UTC)
== [[bêhîkarîkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:01, 1 nîsan 2024 (UTC)
== [[hîkarîkirî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:02, 1 nîsan 2024 (UTC)
== [[hîkarîkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:03, 1 nîsan 2024 (UTC)
== [[hîkarî kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:07, 1 nîsan 2024 (UTC)
== [[rayda]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:40, 1 nîsan 2024 (UTC)
== [[bêrayda]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:42, 1 nîsan 2024 (UTC)
== [[bêraydayî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:43, 1 nîsan 2024 (UTC)
== [[bêraydatî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:43, 1 nîsan 2024 (UTC)
== [[bêrayda bûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:43, 1 nîsan 2024 (UTC)
== [[bêrayda kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:43, 1 nîsan 2024 (UTC)
== [[bêraydabûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:44, 1 nîsan 2024 (UTC)
== [[bêraydakirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:44, 1 nîsan 2024 (UTC)
== [[birayda]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:44, 1 nîsan 2024 (UTC)
== [[biwerêç]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:46, 1 nîsan 2024 (UTC)
== [[biwerêçî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:46, 1 nîsan 2024 (UTC)
== [[biwerêçîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:47, 1 nîsan 2024 (UTC)
== [[biwerêçtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:47, 1 nîsan 2024 (UTC)
== [[werêç]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:49, 1 nîsan 2024 (UTC)
== [[werêç kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:50, 1 nîsan 2024 (UTC)
== [[werêçkirî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:50, 1 nîsan 2024 (UTC)
== [[werêçkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:51, 1 nîsan 2024 (UTC)
== [[wişto]] ==
@[[Bikarhêner:Zerwanda]] Çavkanî ji bo kurmancî tine ye. Zazakî ye? —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:19, 1 nîsan 2024 (UTC)
:ji devera mûşê, aliyê diya min "wişto/wiştik" digotin. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 20:44, 1 nîsan 2024 (UTC)
== [[xewgerî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:19, 2 nîsan 2024 (UTC)
== [[xewgerokî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:28, 2 nîsan 2024 (UTC)
== [[nimêh]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:51, 2 nîsan 2024 (UTC)
== [[فعال]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' [[Bikarhêner:Ghybu|Gyhbu]] di 2021an de li ser rûpela wî tevlî kirîye. Lê ji ber nebûna vê sîstema me tevlî vir nebûye. Lewma min anî ser vir jî. Wê gavê tenê ji bo maneya "kar" ev şablon hatîye tevlîkirin. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:27, 3 nîsan 2024 (UTC)
== [[pêjor]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:46, 6 nîsan 2024 (UTC)
== [[hafil]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:48, 6 nîsan 2024 (UTC)
== [[micemi]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Muhtemelem şiklê rast wek "micemî" ye, lê ew jî bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:52, 9 nîsan 2024 (UTC)
== [[hesaba xwe]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ji xeynî van ev forma wê jî bêçavkanî ne: "[[hisaba xwe]] / [[hesaba xo]] / [[hisaba xo]]". — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:25, 9 nîsan 2024 (UTC)
== [[leşkerma]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:53, 24 nîsan 2024 (UTC)
= Gulan 2024 =
== [[serîi]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Wek "serî" belkî li kar be, lê wekî "serîi" çavkanî peyda nabe. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:20, 2 gulan 2024 (UTC)
== [[virgu]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Bêçavkanî tê xuyan. Peyv bi tirkî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:49, 2 gulan 2024 (UTC)
== [[xwerre]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:57, 20 gulan 2024 (UTC)
= Hezîran 2024 =
== [[di nav deryaya peyvan de avjenî dikir]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:34, 4 hezîran 2024 (UTC)
== [[berbixwe]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:03, 10 hezîran 2024 (UTC)
== [[şipyakî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:08, 10 hezîran 2024 (UTC)
:"şipêkî" heye [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:20, 27 tîrmeh 2024 (UTC)
== [[wetalane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:18, 10 hezîran 2024 (UTC)
== [[wetalî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:19, 10 hezîran 2024 (UTC)
== [[wetalîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:20, 10 hezîran 2024 (UTC)
== [[wetaltî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:20, 10 hezîran 2024 (UTC)
== [[se direye karwan dibuhire]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:49, 16 hezîran 2024 (UTC)
== [[se direwît, karwan bi rêka xo diçît]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Formeke bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:53, 16 hezîran 2024 (UTC)
== [[se direwît, karwan diburît]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Formeke bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:54, 16 hezîran 2024 (UTC)
== [[se direyê karwan dibûrê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Formeke bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:55, 16 hezîran 2024 (UTC)
== [[se direye karvan li riya xwe diçe]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Formeke bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:56, 16 hezîran 2024 (UTC)
== [[se direye û kerwan dibihure]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Formeke bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:00, 16 hezîran 2024 (UTC)
== [[hingê dinya]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:07, 18 hezîran 2024 (UTC)
== [[berdanberdan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:22, 24 hezîran 2024 (UTC)
== [[nîska mala Barzan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:05, 25 hezîran 2024 (UTC)
= Tîrmeh 2024 =
== [[avmêwe]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:41, 16 tîrmeh 2024 (UTC)
== [[nihalok]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:53, 24 tîrmeh 2024 (UTC)
== [[nihalk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:54, 24 tîrmeh 2024 (UTC)
== [[niwalok]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:54, 24 tîrmeh 2024 (UTC)
== [[niwalk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:55, 24 tîrmeh 2024 (UTC)
== [[niwalik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:55, 24 tîrmeh 2024 (UTC)
== [[nehalok]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:56, 24 tîrmeh 2024 (UTC)
== [[nehalk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:56, 24 tîrmeh 2024 (UTC)
== [[nehalik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:57, 24 tîrmeh 2024 (UTC)
== [[alîkarxwazî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:16, 24 tîrmeh 2024 (UTC)
== [[nihalik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:19, 24 tîrmeh 2024 (UTC)
== [[zêrîb]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Bi dîtina min, ev form bêçavkanî ye. Dibe ku ji bo forma "zerîb" çavkanî hebe, lê ew form li ser Wîkîferhengê wek made nehatîye çêkirin. Pir kêm be jî ev peyv wek "zerîp" jî li cihekî derbas bûye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:31, 24 tîrmeh 2024 (UTC)
== [[qanat]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ev peyv bi maneya "kanal"ê nîne, xelet e. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:59, 25 tîrmeh 2024 (UTC)
== [[qenat]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ev peyv bi maneya "kanal"ê nîne, xelet e. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:59, 25 tîrmeh 2024 (UTC)
:Min 1 çavkanî dît, "Ev kes rojê 6-7 qenatên TV ya diğere û diaxive" https://darkamazi.info/archives/1019704 (bi lêgerana "qenatên", ji ber ko lêgeranên dî tijî tiştên dî derdixin)
:Lê mimkun e xelet nivîsîbe. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:31, 27 tîrmeh 2024 (UTC)
== [[şkaftmend]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:08, 25 tîrmeh 2024 (UTC)
:3 çavkanî hene. Lê xuya dike jêdera wan hemû ferhenga tirsik.netê e.
:* Çavkanî 1: https://tirsik.net/ferheng?query=şkaftmend (berî 2014, lê tarîx nîne)
:* Çavkanî 2: https://ku.wikipedia.org/wiki/Deranderî (2011)
:* Çavkanî 3: https://x.com/zerdeceno/status/1155331427510247425 (2019)
:[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:28, 27 tîrmeh 2024 (UTC)
== [[dan hev#p-mane|dan hev]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ev mane li ser çavkanîyan peyda nabe. Tenê çêkerê rûpelê vê maneyê nivîsîye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:08, 25 tîrmeh 2024 (UTC)
== [[baristdar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:47, 25 tîrmeh 2024 (UTC)
== [[baristdarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:48, 25 tîrmeh 2024 (UTC)
== [[baristdarîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:49, 25 tîrmeh 2024 (UTC)
== [[baristdartî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:50, 25 tîrmeh 2024 (UTC)
== [[bibaristî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:51, 25 tîrmeh 2024 (UTC)
== [[bibarist]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:52, 25 tîrmeh 2024 (UTC)
== [[bibaristîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:53, 25 tîrmeh 2024 (UTC)
== [[bêbaristî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:54, 25 tîrmeh 2024 (UTC)
== [[bêbarist]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:55, 25 tîrmeh 2024 (UTC)
== [[peydabûnîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:52, 27 tîrmeh 2024 (UTC)
== [[nexşvan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:05, 27 tîrmeh 2024 (UTC)
== [[nexşvanî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:05, 27 tîrmeh 2024 (UTC)
== [[lêvikî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:47, 27 tîrmeh 2024 (UTC)
== [[lêvik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Min zêde nemeyzand. —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:48, 27 tîrmeh 2024 (UTC)
== [[şerenîx kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:51, 27 tîrmeh 2024 (UTC)
== [[sala çîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:09, 29 tîrmeh 2024 (UTC)
== [[berê pêşîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:57, 30 tîrmeh 2024 (UTC)
== [[berê ewilî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:57, 30 tîrmeh 2024 (UTC)
= Tebax 2024 =
== [[xwîndijmin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:02, 4 tebax 2024 (UTC)
== [[reîsîtî]]/[[reysîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 18:17, 6 tebax 2024 (UTC)
== [[soranîaxivî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 18:43, 6 tebax 2024 (UTC)
== [[helîna bildo]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ji 2020a ve şablon li wir e min tenê li vir zêde kir. —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:56, 6 tebax 2024 (UTC)
== [[hilbijêrînerî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' "Hilbij" rihê peyvê ye, "-er" jî maneya "kesê dike" dide, qertafa "-îner / -înerî" ya li dawîyê bi eşkereyî xelet e. Û ev peyv bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:45, 7 tebax 2024 (UTC)
== [[îqlîmtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:43, 10 tebax 2024 (UTC)
== [[jîrbêjî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ev peyv ger di soranî de ji bo wê çavkanîyên têr hebe, tenê wek "Nivîsîna bi herfên latînî ya peyva ..." kare were qeydkirin. Lê nabe ku wek kurmancî be, were qeydkirin. Ji bo kurmancî tenê di meqaleyekê de derbas dibe, ew jî ji alî başûrîyekî ve hatîye nivîsîn. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:55, 10 tebax 2024 (UTC)
== [[hîgyen]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Tenê ev form bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:40, 12 tebax 2024 (UTC)
== [[kolîner]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:45, 12 tebax 2024 (UTC)
== [[poşmanî kişîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Tenê di çavkanîyekê de peyda dibe. Ew çavkanîya dayî jî ihtîmal e ku bi tesîra zimanê tirkî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:44, 13 tebax 2024 (UTC)
== [[para hevpar a herî mezin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:09, 14 tebax 2024 (UTC)
== [[cara hevpar a herî biçûk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:10, 14 tebax 2024 (UTC)
== [[regjimar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:16, 14 tebax 2024 (UTC)
== [[nîvrengî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:33, 18 tebax 2024 (UTC)
== [[nîvdûrgeh]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Wek "guharto" hatiye qeydkirin, lê yek nikare wilo bêje. Ne "guharto" ye, ji ber wê çavkanî hewce ne. —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:39, 18 tebax 2024 (UTC)
== [[nîvtewlîsî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:44, 18 tebax 2024 (UTC)
== [[nîvrewa]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:44, 18 tebax 2024 (UTC)
== [[nîvşehtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:46, 18 tebax 2024 (UTC)
== [[nîvfalincî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:46, 18 tebax 2024 (UTC)
== [[nîvgilkarî]] ==
Ferhenga Sa.Sa.
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:48, 18 tebax 2024 (UTC)
== [[kesîkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:16, 18 tebax 2024 (UTC)
== [[biryarbirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:38, 24 tebax 2024 (UTC)
= Îlon 2024 =
== [[qîblename]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:15, 1 îlon 2024 (UTC)
== [[cihetnima]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].''
Ji [https://bianet.org/yazi/ferhenga-ser-u-amrazen-ser-ji-kovara-ronahiye-2-196299 vir] hatiye —[[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:27, 1 îlon 2024 (UTC)
== [[qûnderîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:36, 7 îlon 2024 (UTC)
== [[hevpîşehî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:48, 14 îlon 2024 (UTC)
== [[hevpîşeyî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Wek ''navdêr'' bêçavkanî ye. Wek ''rengdêr'' hebe jî, kêmçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:50, 14 îlon 2024 (UTC)
== [[hempay]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:04, 14 îlon 2024 (UTC)
== [[nîs]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:43, 14 îlon 2024 (UTC)
== [[êxbarçî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ji bo ''îxbarçî'' çavkanîyekê peyda dibe, lê kêm e. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:06, 14 îlon 2024 (UTC)
== [[muxbîrîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:38, 14 îlon 2024 (UTC)
== [[ixbarçî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:43, 14 îlon 2024 (UTC)
== [[westgeha pasê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:33, 19 îlon 2024 (UTC)
== [[avikmadk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:24, 19 îlon 2024 (UTC)
== [[ferwerîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:18, 20 îlon 2024 (UTC)
== [[bipayderî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:12, 20 îlon 2024 (UTC)
== [[bipayderîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:13, 20 îlon 2024 (UTC)
== [[bipaydertî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:13, 20 îlon 2024 (UTC)
== [[bipedalî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:14, 20 îlon 2024 (UTC)
== [[bipedalîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:14, 20 îlon 2024 (UTC)
== [[bipedaltî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:14, 20 îlon 2024 (UTC)
== [[pedaldarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:15, 20 îlon 2024 (UTC)
== [[pedaldarîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:15, 20 îlon 2024 (UTC)
== [[pedaldartî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:16, 20 îlon 2024 (UTC)
== [[ciwên]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:11, 22 îlon 2024 (UTC)
== [[belgebirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:31, 23 îlon 2024 (UTC)
== [[avsîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Dibe ku ev kelîme biçavkanî be, lê pir bêagahî hatîye qeydkirin. Biçavkanî be jî, ji ber qeydkirina bêagahî (li ser madeyê agahî kêm hatine pêşkêşkirin) em rûpelê jê bibin. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:53, 23 îlon 2024 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] Ma ne çêtir e li "Daxwazên zelalkirinê" be naxwe? [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 13:40, 10 çiriya paşîn 2024 (UTC)
:: {{r|Guherto}} Bi min em karin vê madeyê ji ber bêagahîbûna wî, îlaweyî "Daxwazên jêbirinê" bikin, lê "Daxwazên zelalkirinê" nabe. Wekî din ji ber ku dikeve çarçoveya her du rûpelan jî kîjan be jî dibe, lê madem me li vir da destpêkirin, bila ji vir dewam bike dengdana li ser vê kelîmeyê. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:22, 10 çiriya paşîn 2024 (UTC)
== [[kûvank]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Herçiqas tenê formekê be jî, bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:14, 27 îlon 2024 (UTC)
== [[kikark]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Herçiqas formekê be jî, bêçavkanî dixuye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:40, 27 îlon 2024 (UTC)
== [[kîfkarik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Herçiqas tenê formekê be jî, bêçavkanî dixuye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:45, 27 îlon 2024 (UTC)
== [[kuvank]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Herçiqas formekê be jî, bêçavkanî dixuye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:53, 27 îlon 2024 (UTC)
== [[kûvarik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Herçiqas tenê formekê be jî, bêçavkanî dixuye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:01, 27 îlon 2024 (UTC)
= Çiriya pêşîn 2024 =
== [[muhil]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Tenê qismê kurdî bêçavkanî ye, ne ya tirkî. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:00, 9 çiriya pêşîn 2024 (UTC)
== [[muhilk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:00, 9 çiriya pêşîn 2024 (UTC)
== [[jêrmal]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:59, 15 çiriya pêşîn 2024 (UTC)
= Çiriya paşîn 2024 =
== [[bîrxistiner]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:38, 14 çiriya paşîn 2024 (UTC)
== [[wisa gotin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ne ferhengî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:13, 15 çiriya paşîn 2024 (UTC)
== [[tenzîlatî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:12, 16 çiriya paşîn 2024 (UTC)
== [[armancparêz]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:03, 19 çiriya paşîn 2024 (UTC)
== [[şikaftmend]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:06, 19 çiriya paşîn 2024 (UTC)
== [[derander]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:08, 19 çiriya paşîn 2024 (UTC)
== [[deranderî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:09, 19 çiriya paşîn 2024 (UTC)
== [[derxwenîn]] ==
@[[User:Keyomerd]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:38, 19 çiriya paşîn 2024 (UTC)
== [[kiyû]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:53, 22 çiriya paşîn 2024 (UTC)
== [[goçavik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:03, 23 çiriya paşîn 2024 (UTC)
== [[hûrhesibaner]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:19, 23 çiriya paşîn 2024 (UTC)
== [[heybera zindî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:01, 23 çiriya paşîn 2024 (UTC)
== [[misadereker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:51, 24 çiriya paşîn 2024 (UTC)
== [[kêmtirkar]] ==
@[[Bikarhêner:Cyrus the virus|Cyrus the virus]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:11, 24 çiriya paşîn 2024 (UTC)
== [[cudagir]] ==
@[[Bikarhêner:Cyrus the virus|Cyrus the virus]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:15, 24 çiriya paşîn 2024 (UTC)
== [[qerwaşgir]] ==
@[[Bikarhêner:Cyrus the virus|Cyrus the virus]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:42, 24 çiriya paşîn 2024 (UTC)
== [[mişkilûr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:45, 24 çiriya paşîn 2024 (UTC)
== [[agirbiran]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:54, 24 çiriya paşîn 2024 (UTC)
== [[alan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:55, 24 çiriya paşîn 2024 (UTC)
== [[alêdû]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:56, 24 çiriya paşîn 2024 (UTC)
== [[henaseker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:37, 25 çiriya paşîn 2024 (UTC)
== [[nial]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ji xeynî vê, maneya 2yem ya di madeya "nihal" de (navdêr 2) jî bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:46, 27 çiriya paşîn 2024 (UTC)
== [[gilçim]] ==
@[[Bikarhêner:Roj Rewandiz|Roj Rewandiz]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:28, 29 çiriya paşîn 2024 (UTC)
:Ez ji bajarê Kobaniyê me. Min ev peyv ji ser zimanê dê û dapîra xwe girt. Ez gelekî ji vê peyvê bawer im ji ber ku devoka min e. [[Bikarhêner:Roj Rewandiz|Roj Rewandiz]] ([[Gotûbêja bikarhêner:Roj Rewandiz|gotûbêj]]) 19:14, 23 kanûna pêşîn 2024 (UTC)
::@[[Bikarhêner:Roj Rewandiz|Roj Rewandiz]] Mala te ava. Ji ber ku çavkaniyên nivîskî tine ne em ê vê kelîmeyê wek [[Wîkîferheng:Pîvekên_tevlîkirinê#Peyvên_herêmî|herêmî]] nîşan bidin. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:22, 25 kanûna pêşîn 2024 (UTC)
== [[reşeme kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Li gorî çêkera sayfeyê li Kobaniyê li kar e. Loma dikare wekî herêmî nîşankirin —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:35, 29 çiriya paşîn 2024 (UTC)
:Li Kobaniyê dibêjin: ‘Tizbiyê min li xwe reş û reşeme kir’ Anko Tizbî bir û venegerand Înkar kir. [[Bikarhêner:Roj Rewandiz|Roj Rewandiz]] ([[Gotûbêja bikarhêner:Roj Rewandiz|gotûbêj]]) 23:48, 7 kanûna paşîn 2025 (UTC)
= Kanûna pêşîn 2024 =
== [[çarkunc]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:21, 27 kanûna pêşîn 2024 (UTC)
= Kanûna paşîn 2025 =
== [[pevnatî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ev kelîme wek kurtkirina ji "bi hev nehatî" hatîye fikirîn, ihtimal e ku wek sunî hatîye çêkirin û bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:43, 6 kanûna paşîn 2025 (UTC)
== [[lihevnatîbûn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Wek formeke "lihevnehatîbûn"ê hatîye fikirîn, lê bêçavkanî ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:49, 6 kanûna paşîn 2025 (UTC)
== [[Binxetê]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Em zanin ku ji ber sînora Tirkîyeyê ku Cizîrê dike du perçe û ji parçeya binî re Binxet tê gotin ku kurd lê dijîn. Feqet min herêma Binxetê seh nekirîye, û bi tedqîqeke neberfireh jî çavkanî peyda nekir ji bo vê peyvê. Lewma bi min mahîyeta vê madeyê û qeydkirina wek "Binxetê" xelet e. Ger ji bo herêma tarîfkirî çavkanî nîn be, em vê madeyê bikin "'''Binxet'''" û tarîfa wê jî bikin "''Navê Rojavayê Kurdistanê ku li binê xeta Tirkîye - Sûrîyeyê dimîne.''" — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:22, 24 kanûna paşîn 2025 (UTC)
== [[xirecir çêkirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' "''Çê''" bi maneya "''baş''" e, û "''çêkirin''" ji bo tiştên pozîtîv likar e, li nav milet jî ev wisa ye. Lewma ev xelet e. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:00, 24 kanûna paşîn 2025 (UTC)
== [[sîrkxane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:30, 26 kanûna paşîn 2025 (UTC)
== [[anînder]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Di hin çavkanîyan de wek formeke "''der'''''anîn'''"ê hatîye qeydkirin. Lê belê çavkanîyeke eslî (ne ji kitêbên gramerê) peyda nabe ji bo vê kelîmeyê. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:24, 27 kanûna paşîn 2025 (UTC)
== [[rastî nîşandan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:14, 30 kanûna paşîn 2025 (UTC)
= Sibat 2025 =
== [[pir hindik#p-mane|pir hindik]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:31, 3 sibat 2025 (UTC)
== [[anteroz]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:19, 15 sibat 2025 (UTC)
== [[veşidandin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:43, 15 sibat 2025 (UTC)
== [[karîwer kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 02:01, 16 sibat 2025 (UTC)
== [[hil girtin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:06, 17 sibat 2025 (UTC)
== [[wer girtin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:07, 17 sibat 2025 (UTC)
== [[avkûre]] ==
@[[Bikarhêner:Mohajeer|Mohajeer]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:36, 18 sibat 2025 (UTC)
== [[derdek girtin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:42, 18 sibat 2025 (UTC)
== [[vekitandin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:12, 21 sibat 2025 (UTC)
== [[dînperist]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:32, 22 sibat 2025 (UTC)
== [[ayînperist]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:33, 22 sibat 2025 (UTC)
== [[ayînperistî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:33, 22 sibat 2025 (UTC)
== [[ayînperistane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:33, 22 sibat 2025 (UTC)
== [[piştrastkirinbar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:45, 23 sibat 2025 (UTC)
== [[diyarkirinbar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 02:08, 23 sibat 2025 (UTC)
== [[hevhatî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 03:52, 1 adar 2025 (UTC)
== [[kerexne]] ==
@[[Bikarhêner:Rojhilat|Rojhilat]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:05, 1 adar 2025 (UTC)
== [[pawan#p-mane|pawan]] ==
Ferhenga Hejar soranî ye, ji bo maneya "cihê qedexekirî, cihê inhisarî" ji xeynî wê çavkanî heye gelo —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:17, 3 adar 2025 (UTC)
== [[taqane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 02:11, 6 adar 2025 (UTC)
== [[gosirmetî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:07, 6 adar 2025 (UTC)
== [[matîjêker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:08, 14 adar 2025 (UTC)
== [[hilewesan]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:14, 16 adar 2025 (UTC)
== [[peydandin#p-mane|peydandin]] ==
Ji bo maneya [[dûre]], [[paşî]], [[piştî]] wê / wî ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:26, 19 adar 2025 (UTC)
== [[peşar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:36, 19 adar 2025 (UTC)
== [[nûzaf]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:30, 20 adar 2025 (UTC)
== [[deynname]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:08, 20 adar 2025 (UTC)
== [[girtek]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:29, 20 adar 2025 (UTC)
== [[bêşxane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:25, 23 adar 2025 (UTC)
== [[superleheng]] ==
@[[Bikarhêner:Avestaboy|Avestaboy]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:34, 23 adar 2025 (UTC)
== [[goranparêz]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:54, 27 adar 2025 (UTC)
== [[raber#p-mane|raber]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:09, 1 nîsan 2025 (UTC)
== [[morîvanî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:06, 1 nîsan 2025 (UTC)
== [[wevde]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:17, 3 nîsan 2025 (UTC)
== [[şandêr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:31, 3 nîsan 2025 (UTC)
== [[giloverî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Tenê li ser Ferhenga Zaravên Teknîkî û hejmara yekem ya kovara Bernamegehê derbas dibe. —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:10, 4 nîsan 2025 (UTC)
== [[xêvjimêr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:00, 12 nîsan 2025 (UTC)
== [[paşekeft]] ==
@[[Bikarhêner:Jiyar Hesen|Jiyar Hesen]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:11, 13 nîsan 2025 (UTC)
== [[paşekeft kirin]] ==
@[[Bikarhêner:Jiyar Hesen|Jiyar Hesen]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:12, 13 nîsan 2025 (UTC)
== [[hawirkanî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:36, 20 nîsan 2025 (UTC)
== [[hawirkan]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:18, 21 nîsan 2025 (UTC)
: Di çavkanîyeke de "''hawrikan''" (ku wek forma vê kelîmeyê hatîye nîşandayîn û wek "hevrêyan" a kurmancî ye) bi maneya "''friends''"a îngilîzî hatîye emilandin. Wekî din ti çavkanî nîne ji bo vê peyvê. Ger ev "hevrik"a kurmancî be, li vir dîsa xelet e. Bi her awayî divê were jêbirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:28, 21 nîsan 2025 (UTC)
== [[hawirkanî]] ==
@[[Bikarhêner:Cyrus the virus|Cyrus the virus]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:18, 21 nîsan 2025 (UTC)
== [[hawirkanvanî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:18, 21 nîsan 2025 (UTC)
== [[riberiz]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:39, 21 nîsan 2025 (UTC)
:Ez bawer im ku ev hat peyitandin. Min çavkanî di beşa gotûbêjê de dan û şablona bêçavkanîtiyê rakir. [[Bikarhêner:Ferhengvan|Ferhengvan]] ([[Gotûbêja bikarhêner:Ferhengvan|gotûbêj]]) 06:11, 25 nîsan 2025 (UTC)
== [[awam]] ==
@[[User:Ferhengvan|Ferhengvan]] Min ev kelîme di ferhengên din de nedît. Gelo di kurmancî de tê bikaranîn? —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:09, 23 nîsan 2025 (UTC)
:@[[Bikarhêner:Zerwanda|Zerwanda]] tu jî forma [[vam]] ya vê kelîmeyê diemilînî, gelo çavkanî ji bo wê jî heye? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:11, 23 nîsan 2025 (UTC)
::Min jî li vir peyda kir û dest kar anîn kir, hemin ev li guherrên dengî ên Kurmancî dihat (*ebam > *ewam > *wam > vam; wek *ebaj- > {{q|ku|vajî}}). Min gotibû qey di zarawayê kesekî da hebûye û wî bi ser va kiriye û jixwe nepêkan nîn e ku di zimên da hebe îcar rind e, nê min jî serkanî tine ji bonê. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 21:37, 23 nîsan 2025 (UTC)
:::https://bnk.institutkurde.org/images/pdf/4UVB2YMZG3.pdf
:::Di vê kitêbê de bi forma wam derbas dibe 1 Wam : qerz, dên. " dîsa jî gereg 3 çavkanî hebe.
:::Lê di beşa etîmolojiyê de eger em karibin zimanekî yekgirtî biemilînin, dê baştir be. Wekî din "bi vê enflasyona kelîmeyan" em nikarin ti tiştî bidin fêmkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:54, 23 nîsan 2025 (UTC)
== [[arvanê hûr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:48, 27 nîsan 2025 (UTC)
== [[damezrak]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:47, 27 nîsan 2025 (UTC)
== [[mezrak]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:49, 27 nîsan 2025 (UTC)
== [[hevhemetî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:25, 6 gulan 2025 (UTC)
== [[navhemetî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:25, 6 gulan 2025 (UTC)
== [[entîte]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:08, 17 gulan 2025 (UTC)
== [[agirvekujêner]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:35, 21 gulan 2025 (UTC)
== [[progres]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:50, 23 gulan 2025 (UTC)
== [[bûncî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:29, 28 gulan 2025 (UTC)
== [[tasît]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:36, 31 gulan 2025 (UTC)
== [[implîcît]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:37, 31 gulan 2025 (UTC)
== [[hewedar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:14, 31 gulan 2025 (UTC)
== [[destajo]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:38, 31 gulan 2025 (UTC)
== [[binhimbank]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:23, 1 hezîran 2025 (UTC)
== [[şîmane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:31, 2 hezîran 2025 (UTC)
== [[materya]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:12, 5 hezîran 2025 (UTC)
== [[bêtî]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:03, 7 hezîran 2025 (UTC)
== [[rîbat]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:21, 9 hezîran 2025 (UTC)
== [[hevtawan]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:26, 12 hezîran 2025 (UTC)
:Pirsek: "Hevwate" kelîmeyên ku maneyan wan hev in. "hevtawan" çawa dibe "şirîkên sûcekê"? Gereg sûcên ku tiştê wan hev bin? Ew tişt çi ye? Çima dibe şirîk? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:32, 12 hezîran 2025 (UTC)
::Merheba!
::"Hevtawan" anku "şirîkên heman tawanê, kesên ku heman tawan kiriye/dikin", wek "hevkar = şirîkên heman karî, kesên ku heman karî dikin", "hevjîn = şirîkên heman jiyanê, kesên ku bi hev re dijîn"... [[Bikarhêner:Ferhengvan|Ferhengvan]] ([[Gotûbêja bikarhêner:Ferhengvan|gotûbêj]]) 06:11, 30 hezîran 2025 (UTC)
== [[zanîş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:07, 14 hezîran 2025 (UTC)
== [[zanîşwer]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:07, 14 hezîran 2025 (UTC)
== [[zanmend]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:09, 14 hezîran 2025 (UTC)
== [[zandar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:08, 14 hezîran 2025 (UTC)
== [[zayîndar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:12, 14 hezîran 2025 (UTC)
== [[zayindar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:12, 14 hezîran 2025 (UTC)
== [[akund]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:16, 16 hezîran 2025 (UTC)
== [[destbikar]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:41, 22 hezîran 2025 (UTC)
== [[vekitandin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:DarkAngel2100|DarkAngel2100]] ([[Gotûbêja bikarhêner:DarkAngel2100|gotûbêj]]) 10:45, 2 tîrmeh 2025 (UTC)
Çavkanî: Kurdish-English Dictionary by Baran Rizgar
== [[peşarmend]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:54, 5 tîrmeh 2025 (UTC)
== [[zaîd]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:59, 6 tîrmeh 2025 (UTC)
== [[Xrîst]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:13, 10 tîrmeh 2025 (UTC)
== [[hesibanî kirin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:48, 13 tîrmeh 2025 (UTC)
== [[top danîn]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:08, 13 tîrmeh 2025 (UTC)
== [[çûkger]] ==
@[[Bikarhêner:Dûrzan|Dûrzan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:49, 18 tîrmeh 2025 (UTC)
== [[dûneçkar]] ==
@[[Bikarhêner:212.175.166.246|212.175.166.246]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:38, 10 tebax 2025 (UTC)
== [[sermirov]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:22, 13 tebax 2025 (UTC)
== [[nifira Esed]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:00, 19 tebax 2025 (UTC)
== [[dilkrîz]] ==
@[[Bikarhêner:Kurd87612|Kurd87612]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:44, 23 tebax 2025 (UTC)
== [[berxa Xwedê]] ==
@[[Bikarhêner:Kurd87612|Kurd87612]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:28, 31 tebax 2025 (UTC)
== [[destmatî]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:47, 21 îlon 2025 (UTC)
== [[sîbaq]] ==
@[[Bikarhêner:Ferhengvan|Ferhengvan]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:33, 27 îlon 2025 (UTC)
== [[mirovê daristanê]] ==
@[[Bikarhêner:Hewarwîkîferheng|Hewarwîkîferheng]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:09, 18 çiriya pêşîn 2025 (UTC)
== [[çavbijîşk]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:30, 24 çiriya pêşîn 2025 (UTC)
== [[makeşar]] ==
@[[Bikarhêner:Avestaboy|Avestaboy]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:46, 14 kanûna pêşîn 2025 (UTC)
:Silav @[[Bikarhêner:Bikarhêner|Bikarhêner]] min bi derew peyv çê nekir. Peyva makeşar di sepana O-ferheng û her wiha di ferhenga online ferhengkurdi.org ya '''Enstîtuya Kurdî ya Stenbolê''' tê xwendin.
:https://ferhengkurdi.org/tr/kelime/make%C5%9Far-n [[Bikarhêner:Avestaboy|Avestaboy]] ([[Gotûbêja bikarhêner:Avestaboy|gotûbêj]]) 11:07, 18 kanûna pêşîn 2025 (UTC)
::Krîterên me yên tevlîkirina kelîmeyan heye. Ev made rîayeta wan krîteran nake. Lewma min vê kelîmeyê li vir bicih kir. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:28, 18 kanûna pêşîn 2025 (UTC)
== [[bihîstinzanî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:30, 23 hezîran 2026 (UTC)
== [[hevdilî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:28, 24 hezîran 2026 (UTC)
== [[kemisandin]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:45, 24 hezîran 2026 (UTC)
== [[kexme]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:53, 24 hezîran 2026 (UTC)
== [[lêpirsyarker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:32, 24 hezîran 2026 (UTC)
== [[raypirs]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:34, 24 hezîran 2026 (UTC)
== [[helwestpirs]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:34, 24 hezîran 2026 (UTC)
== [[kopirî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' xuyaye maneyeke wekî pir jî heye dikare were zêdekirin —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:33, 25 hezîran 2026 (UTC)
== [[merxeme]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:55, 25 hezîran 2026 (UTC)
== [[mohl]] ==
@[[Bikarhêner:Far ati|Far ati]] ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:14, 25 hezîran 2026 (UTC)
== [[irfandarî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:30, 26 hezîran 2026 (UTC)
== [[axulme]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:20, 30 hezîran 2026 (UTC)
== [[şewane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:26, 1 tîrmeh 2026 (UTC)
== [[poezî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:27, 1 tîrmeh 2026 (UTC)
== [[sebaxker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:20, 1 tîrmeh 2026 (UTC)
== [[sebaxfiroş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:21, 1 tîrmeh 2026 (UTC)
== [[siboxfiroş]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:21, 1 tîrmeh 2026 (UTC)
== [[diyardenasîtî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:26, 1 tîrmeh 2026 (UTC)
== [[dawxane]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:41, 3 tîrmeh 2026 (UTC)
== [[pêşajotî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:15, 3 tîrmeh 2026 (UTC)
== [[kontînentaltirîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:35, 3 tîrmeh 2026 (UTC)
== [[azmayîşker]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:16, 3 tîrmeh 2026 (UTC)
== [[ateşparêz]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 05:19, 4 tîrmeh 2026 (UTC)
== [[kurtetarîx]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:52, 4 tîrmeh 2026 (UTC)
== [[besayetî]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:57, 4 tîrmeh 2026 (UTC)
== [[tawbijêr]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:45, 4 tîrmeh 2026 (UTC)
== [[destbir]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:30, 5 tîrmeh 2026 (UTC)
== [[nekider]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:56, 5 tîrmeh 2026 (UTC)
== [[niqar]] ==
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:39, 5 tîrmeh 2026 (UTC)
== [[parêzîn]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' Ev form li ti derê peyda nabe. Berî çendîn salan hatîye çêkirin. Bi qenaeta min şaş e, lewma em jê bibin. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:29, 6 tîrmeh 2026 (UTC)
== [[stêrfizîk]] ==
. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:33, 6 tîrmeh 2026 (UTC)
== [[pêçavik]] ==
. Eger ev kelîmeyeke mehelî be, Jkx şablona {{şx|dever|cihê ku lê tê bikaranîn}} li madeyê zêde bike. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:59, 7 tîrmeh 2026 (UTC)
== [[qerqef]] ==
. Eger ev kelîmeyeke mehelî be, Jkx şablona {{şx|dever|cihê ku lê tê bikaranîn}} li madeyê zêde bike. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:17, 7 tîrmeh 2026 (UTC)
== [[rakar]] ==
. Eger ev kelîmeyeke mehelî be, Jkx şablona {{şx|dever|cihê ku lê tê bikaranîn}} li madeyê zêde bike. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:30, 7 tîrmeh 2026 (UTC)
== [[remilîn]] ==
. Eger ev kelîmeyeke mehelî be, Jkx şablona {{şx|dever|cihê ku lê tê bikaranîn}} li madeyê zêde bike. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:36, 7 tîrmeh 2026 (UTC)
== [[segurî]] ==
. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:51, 7 tîrmeh 2026 (UTC)
== [[serdev]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:10, 7 tîrmeh 2026 (UTC)
== [[serziman]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:01, 7 tîrmeh 2026 (UTC)
== [[tenîsk]] ==
di Înternetê de gelek netîce derdikevin lê ji bo ku dûre ez kontrol bikim û çavkaniyên sehîh binivîsim li vir îlawe dikim. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:53, 7 tîrmeh 2026 (UTC)
== [[tertî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:58, 7 tîrmeh 2026 (UTC)
== [[tirut]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:25, 7 tîrmeh 2026 (UTC)
== [[tizqa]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:28, 7 tîrmeh 2026 (UTC)
== [[têkilîdêr]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:31, 7 tîrmeh 2026 (UTC)
== [[tîtalk]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:33, 7 tîrmeh 2026 (UTC)
== [[vircoq]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:45, 7 tîrmeh 2026 (UTC)
== [[xilêbat]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]]. Li gorî kurteya çêkirina sayfeyê li Qamişlo û Amûdê tê bikaranîn. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:14, 7 tîrmeh 2026 (UTC)
== [[xin_û_cûc]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:27, 7 tîrmeh 2026 (UTC)
== [[çiteba]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:38, 7 tîrmeh 2026 (UTC)
== [[desttêl]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 08:45, 8 tîrmeh 2026 (UTC)
== [[Mîmedol]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 08:51, 8 tîrmeh 2026 (UTC)
== [[alaf]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:01, 8 tîrmeh 2026 (UTC)
== [[aşirme]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:13, 10 tîrmeh 2026 (UTC)
== [[geledîmentî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:54, 13 tîrmeh 2026 (UTC)
== [[çendîmentî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:54, 13 tîrmeh 2026 (UTC)
== [[rojeng]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:41, 18 tîrmeh 2026 (UTC)
== [[bereyîşk]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:47, 19 tîrmeh 2026 (UTC)
== [[wesekname]] ==
''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:08, 22 tîrmeh 2026 (UTC)
== [[cemaetgeh]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:17, 24 tîrmeh 2026 (UTC)
== [[iblîskanî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:27, 30 tîrmeh 2026 (UTC)
== [[pehnik]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:51, 4 tebax 2026 (UTC)
:# Kamiran bedirxan û H. Muhammed bi maneya guwerte bi kar tînin.
:# Navê Çiyayek e li Hawarê derbas dibe
:# Farqînî tenê dibêje "yassı"
:#* [https://usercontent.one/wp/portal.arsivakurd.org/wp-content/uploads/2021/08/Azadiya-welat_67.pdf?media=1709107483 Azadiya welat hj 67] Roşan Lezgîn kevirên pehnik
:# Ferhenga destî pahnik pehnik dibêje
:# Rojen barnas li vir bi çi mana bi kar tîne?
:#* {{jêder-kitêb|ku|jêgirtin=... '''pehnika''' dirandar tê kişandin û ji nû ve bi mîxên têlkî ve tê girêdan ku firk û sistî nekevê. Piştî ku dirûtina rû bi binpiyê hundirîn ve xelas bû vêca dora binpiyê derva (binî) tê. Binpiyê binî li ser yê hun- dirîn an bitenê bi...|sal=2013|nivîskar=Rojen Barnas, Nûbihar|url=http://books.google.com.tr/books?id=pVDKEAAAQBAJ&pg=PA67&dq=%22wan+pehnikan%22+OR+%22pehnik%C3%AA%22+OR+%22pehnikin%22+OR+%22pehnikek%C3%AA%22+OR+%22pehnika%22+OR+%22w%C3%AA+pehnik%C3%AA%22+OR+%22pehnikino%22+OR+%22pehnikine%22&hl=&cd=4&source=gbs_api|sernav=Sîlvan/ Silîvan / Farqîn|weşanxane=PAK AJANS YAYINCILIK TURİZM VE DIŞ TİCARET LİMİTED ŞİRKETİ|ISBN=9786055402884|rûpel=67}}
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:40, 6 tebax 2026 (UTC)
== [[temirîndox]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:56, 6 tebax 2026 (UTC)
== [[kansêrolojî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:17, 6 tebax 2026 (UTC)
== [[navçeparêzî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:09, 6 tebax 2026 (UTC)
== [[goganîbûnî]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:23, 15 tebax 2026 (UTC)
== [[zewijandin]] ==
Bêyî çavkaniyan e. --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 18:34, 22 tebax 2026 (UTC)
== [[karbes]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 12:42, 29 tebax 2026 (UTC)
== [[biehliyet]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:41, 31 tebax 2026 (UTC)
== [[pofêldang]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 07:09, 10 îlon 2026 (UTC)
== [[jenk]] ==
Ji xeynî Farqînî ti çavkanî nîne. Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:29, 19 îlon 2026 (UTC)
== [[bedewnivîs]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:20, 20 îlon 2026 (UTC)
== [[spehînivîs]] ==
Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:21, 20 îlon 2026 (UTC)
tqr7pefqyjord20ynwuoih5zr25qzeg
Wîkîferheng:Daxwazên standardkirinê
4
845699
6535039
6265702
2026-09-21T06:59:59Z
Kurdistanov
15290
/* Gotûbêj 3 */ Bersiv
6535039
wikitext
text/x-wiki
{{/destpêk}}
= Daxwazên standardkirinê: 2020 =
== [[mizhar]] ==
mizhar / miz'ar / mizar --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:31, 10 tebax 2020 (UTC)
===Gotûbêj===
{{şîrove}}
Min gelek ferhangan nêrî lê min kelîmeyek wisa nedît. Divê ev kelîme were peyitandin. Wexta em kelîmeyek bo standartkirinê hilbijêrin divê hinek ser bisekinin. Ka kelîme hêjaye an na. Kelîmeyê zêde tên bikaranîn divê were hilbijêrîn.--[[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 20:54, 3 çiriya paşîn 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[kîte]] / [[kite]] ==
[[kîte]] / [[kite]] Divê werin standardkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:11, 13 tebax 2020 (UTC)
{{rfk||}} Di vê ferhengê de "kîte / kite" nîne, lê wek lêker cih dane "kîte kirin"ê. Jê tê famkirin ku halê standard wek "'''kîte'''" dibînin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:31, 28 nîsan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ji bo '''''[[kit]]''''' ez dibêjim '''''[[kit]]'''''; nabêjim '''''[[kît]]'''''. Ma '''''[[kîte]]''''' ('''''[[kît]]''''') li kû derê li kar e? -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 15:13, 5 îlon 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[hêvişandin]] ==
[[hêvişandin]] / [[êvişandin]] / [[hewişandin]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:50, 15 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[hindirandin]] ==
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 09:09, 16 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
*Bi ya min divê forma ''hundirandin'' bibe standard. Li herêma Behdînan gelek caran '''u''' dibe '''i''', '''û''' jî dibe '''î'''. Loma [[hindir]] weke rûpela sereke hatiye çêkirin.—<small>[[Bikarhêner:Gomada|<span style="background:Yellow">'''''<span style="font-family:Verdana;color:red">Gom<span style="color:#006400">ada</span></span></span>''''']]<sup>[[Gotûbêja bikarhêner:Gomada|peyam]]</sup></small> 15:40, 21 kanûna paşîn 2021 (UTC)
** {{silav|Gomada}}. Bnr. [[WF:Rêzikên standardkirinê]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:42, 21 kanûna paşîn 2021 (UTC)
== [[zingar]] ==
Eynî rûpel li [[zeng]] û [[jeng]] heye. Ez awayê rast wek [[zingar]] dibînim. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 12:34, 18 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[axaftin]] ==
[[axaftin]] / [[axivtin]] / [[axiftin]] / [[axivîn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:23, 19 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Axiftin û axivîn ne eynî tişt in. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:05, 22 tebax 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[jêbirk]] ==
[[jêbir]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:41, 19 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Xuya dike paşgira -k (bi vê maneyê) gelekî nayê bikaranîn. Agirkuj bi dêla agirkuj-k tê bikaranîn, min tenê "jêbir" bihîstîye, "xwêdan-k" bêçavkanî ye, "demjimêr-k" kêm peyda dibê (bêhtir di spam de heye) ... -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:39, 22 tebax 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[bager]] ==
[[bager]] / [[bagêr]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:16, 19 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[çikilandin]] ==
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:52, 20 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[evraz]] ==
[[evraz]] / [[hevraz]] / [[evrazî]] / [[hevrazî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:03, 20 tebax 2020 (UTC)
:ervaz [[Taybet:Beşdarî/~2026-16469-22|~2026-16469-22]] ([[Gotûbêja bikarhêner:~2026-16469-22|talk]]) 19:30, 15 adar 2026 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[xwişk]] ==
[[xwişk]] / [[xweng]] / [[xwek]] / [[xuşk]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:33, 21 tebax 2020 (UTC)
{{rfk|xwişk, xweng, xweh|r=524 (xweng, xweh), 526 (xwişk)}}
Di ferhengê de "xwişk (r. 526), xweng (r. 524), xweh
(r.524)" tev jî bi maneya "''kız kardeş''" hatine qeydkirin. "xwek" û "xuşk" jî tê de nîne. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:04, 2 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} ''[[xuşk]]'' u ''[[xweh]]'' ji ber ku di devoka min da herdu jî têne bikaranîn, wek ''[[xuçik]]'' u ''[[xweng]]''. ''[[xwek]]'' devokî ye u bipiranî li rojavayê tê bikaranîn. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 15:49, 21 tebax 2020 (UTC)
: {{silav|Hakîmo}} Kengî dengdana li ser [[Wîkîferheng:Rêzikên standardkirinê|Rêzikên standardkirinê]] xilas bû, em ê dest bi standardkirinê bikin. Tu jî dikarî fikrên xwe li vir bibêjî '''[[Wîkîferheng:Dengdan/Rêzik/Rêzikên standardkirinê]]'''.
::Di devoka min de xwîşk, xuh û xwak hene. Di wextên cuda de tên bikaranîn. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 16:06, 21 tebax 2020 (UTC)
Ez awayê rast ya xwîşk/xwişk weke "xwîşk" dibînim, û di înternetê de bêhtir encam hene (hejmara rûpelan binêrin, hejmara encaman şaş e.) -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:27, 25 tebax 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin.
{{erê}}: [[xuşk]] u [[xweh]] (belkî [[xuçik]] jî, lê niha [[xuşk]] ancax têr dikê)-->
:Silav hevalên hêja, ez nizanim çawa deng bidim, lê ji bo standartkirinê divê mirov binere kîjan peyv kevintir e, ji bo vê yekê ez dengê xwe didim peyva '''xweh''' [[Bikarhêner:Kurdistanov|Kurdistanov]] ([[Gotûbêja bikarhêner:Kurdistanov|gotûbêj]]) 06:59, 21 îlon 2026 (UTC)
== [[xwendevan]] ==
[[xwendevan]] / [[xwandevan]] / [[xwêndkarî]] / [[xwîndkarî]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:50, 21 tebax 2020 (UTC)
{{rfk|xwendevan, xwendekar|r=524}} Di ferhengê de bi maneyên cuda her du peyv jî derbas dibin. "xwendekar" bi maneya "öğrenci, talebe"; "xwendevan" bi maneya "okuyucu, okur" hatîye qeydkirin. Lewma divê her du jî hebin. "''xwêndkarî''" û "''xwîndkarî''" jî formên "xwendekarî"yê ne, lewma divê wek "xwendekarî" werin standardkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:52, 2 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Xwendevan û xwendekar bi eynî maneyî ne lê ne eynî peyv in, nikarin weke yek werin standardkirin. Ji ber ku xwendin/xwandin weke xwendin hatîye standardkirin divê xwendevan jî wilo bibê. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:05, 4 îlon 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[sefhe]] ==
[[sefhe]] / [[seyfe]] / [[sayfe]] -- [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:34, 22 tebax 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:06, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ya standard "sehîfe" ye. Forma "sefhe" di şiîrên Ehmedê Xanî de derbas dibe, lê îro ev peyv bi forma "sehîfe" tê zanîn. Lewma bi min divê em awayê standard wek "'''sehîfe'''" nîşan bidin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:06, 1 hezîran 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[pergala xebatê]] ==
[[pergala xebatê]] / [[pergala xebitandinê]]
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:29, 26 tebax 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:49, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ya rast ev ne pirseke li ser standardkirinê ye. Lewma li vir neyê minaqeşekirin baştir e. Lê dîsa fikrê xwe bêjim. Îngilîzîya wî "''operating system''" e, ew jî bi kurdî him dibe "''sîstema xebitandinê''" him jî "''sîstema xebitînê''", ji ber ku lêkera "operate" him transîtîv e him jî întransîtîv. Bi min "pergala xebatê" jî ne xelet e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:49, 1 hezîran 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[qet]] ==
Navdêra qet/qed (bi maneya parçe).
Ji erebî hatîye û ya erebî "qet" e (bi t-ya qalin weke '''t'''ewandin). Ya kurdî bi t-ya normal e. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 18:24, 28 tebax 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:02, 28 nîsan 2025 (UTC)
Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de 'qed' derbas nabe, qet bi maneya 'hîç' derbas dibe, halê standard ya 'qet'a bi maneya 'parçe'yê wekî 'qut' nîşan dane. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:07, 28 nîsan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
* Hiii, min got yanî ya hoker bû. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 14:03, 29 tebax 2020 (UTC)
:{{r|Hakîmo}} Min şablonê li navdêrê danîbû lê baştir e ez binivîsim ku li ser navdêrê ye. (Min vêce nivîsî). -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:30, 29 tebax 2020 (UTC)
{{şîrove}} Wek min li jor nivîsî, ev peyv di ferhenga ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de tine ye, feqet bi min divê forma "'''qet'''" wek ya standard were qebûlkirin. Wek notek biçûk vê jî bêjim, ji "qet"a bi maneya "parçe" lêkera "qet kirin"ê jî çêbûye û lêkera "qut kirin"ê ku tê maneya "du tiştan ji hev qet kirin" û lêkera "qet kirin"ê bi eynî maneyê ne. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:02, 28 nîsan 2025 (UTC)
{{şîrove}} Li ser meseleya 'qet' û 'qut'ê jî, her du jî pir berbelav in, lewma bi min divê her du jî bimînin. Wekî din hin ferq jî hene di navbera wan de, wek nimûne, halê navdêrî ya "qet"ê berbelav e, lê halê navdêrî ya "qut"ê neberbelav e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:07, 28 nîsan 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[darberû]] / [[darberî]] ==
[[darberû]] / [[darberî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:36, 30 tebax 2020 (UTC)
{{rfk|berû|r=73}} Peyva "darberû" nîne, lê "berû" heye. Jixwe peyda "dar" standard e, nelihevî li ser qismê "berû" û "berî"yê ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:24, 27 nîsan 2025 (UTC)
{{ds-kirî|darberû}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:06, 1 hezîran 2025 (UTC)
== [[diran]] / [[didan]] ==
[[diran]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:52, 31 tebax 2020 (UTC)
{{rfk|diran|r=136}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:20, 27 nîsan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} '''''[[didan]]''''' awayê standard e ji ber ku '''''[[diran]]''''' ji wê hatiye: '''''didan''''' > '''''diran'''''
* Li Qonyayê herdu li kar in (ez '''''[[didan]]''''' bi kar tînim); li rojhilatê '''''[[didan]]''''' pirtir li kar e; Ehmedê Xanî di ''Nûbihara Biçûkan'' da '''''[[didan]]''''' bi kar anîbû. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 08:29, 1 îlon 2020 (UTC)
* {{erê}} [[didan]] -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 08:29, 1 îlon 2020 (UTC)
Em dibêjin diran û dinan. "Didan" formeke lêkerê ye jî ("ew xwarin ''didan'' xelkê"), baştir e ne weke hev bin.
Diran (înternetê bigere "diranê" û "diranên") gelekî bêhtir ji "didan" ("didanê", "didanên") tê bikaranîn.
Fikirên [[Bikarhêner:Ferhengvan|Ferhengvan]] (23ê îlonê 2019) li vir hene: [[Gotûbêja pêvekê:fînî-kurdî]]. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:02, 1 îlon 2020 (UTC)
{{şîrove}} {{r|Guherto|Hakîmo}} Hevalno hûn tim behsa bilêvkirinê dikim lê hûn ti carî behsa çavkaniyan nakin. Binêrin [[Wîkîferheng:Dengdan/Rêzik/Rêzikên standardkirinê]] dibêje ''Derbarê awayê peyvekê de çiqas zêde çavkaniyên kitêb an ferhengên rastnivîsînê, ferheng û kovarên zimannasiyê hebin, ew awa wek awayê standard tên pejirandin.'' Yanî bilêvkirina xelkê ne xema me ye. Heta niha 4 kes gotine erê, ku hûn naxwazin em bi ya çavkaniyan bikin hingê dengê xwe bidin, bibêjin na daku ew rêzik nayên pêjirandin û em dîsa gotûbêj çêkin, rêzikên din hilbijêrin. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:11, 1 îlon 2020 (UTC)
::{{r|Balyozxane}} Min gotibû ''Diran (înternetê bigere "diranê" û "diranên") gelekî bêhtir ji "didan" ("didanê", "didanên") tê bikaranîn.''. Ew li ser çavkanîya ne. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:26, 1 îlon 2020 (UTC)
:::{{r|Guherto}} Hingê çima dengê xwe nadî? --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:27, 1 îlon 2020 (UTC)
::::{{r|Balyozxane}} Min hin sanîye berî ku tu vê peyama xwe ya talî şand dengê xwe da :) -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:28, 1 îlon 2020 (UTC)
:::::{{r|Guherto}} Mala te ava! :) --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:30, 1 îlon 2020 (UTC)
::::::{{r|Balyozxane}} Merheba. Min berê jî gotibû: ''rewşa kurmancên me gelek xirab e, u ji ber hindê pir kurmancên me bi xwendin u nivîsîna zimanê xwe nizanin''. Çavkaniyên niha bipiranî ji devokên navendî tên, hinên rojhilatê jî bi herfên erebî ne. Zimanê standard bi çavkaniyan çênabit, divêt li gorî zimanê hemî kurmancan çêbit. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 16:19, 2 îlon 2020 (UTC)
::::::{{r|Balyozxane}} Herwiha '''''[[didan]]''''' helbet dikarit wek '''''[[diran]]''''' bi lêv bibit, lê berevajî nabit. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 16:22, 2 îlon 2020 (UTC)
:::::::{{r|Hakîmo}} Merheba, fikirên xwe li dengdanê çima nadê? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 16:44, 2 îlon 2020 (UTC)
::::::::{{r|Guherto}} Ne min dayê... -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 12:53, 3 îlon 2020 (UTC)
Ez dibêjim ku "dinan" formêka rasttirin bi kurdî ye (bi kelhûrî bikar tînin). Em berhevdin:
Farsî: duzd, nazdīk, gandum, gand, dandān, xand-īd-an
Kurdî: diz, nezîk, genim, genî, dinan, ken-î-n
Zazayî: dızd, nezdi, genım*, gende, dından, (hûwaene)
Bi kurmancî, soranî u kelhûrî ZD u ND dibin Z u N, D dikeve. Ev qeyde gelêk kevin e.
*ji kurmancî, bi taliş qender: gāndım
[[Bikarhêner:Umut Akkoç|Umut Akkoç]] ([[Gotûbêja bikarhêner:Umut Akkoç|gotûbêj]]) 14:14, 18 gulan 2021 (UTC)
{{ds-kirî|diran}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:07, 1 hezîran 2025 (UTC)
== [[dur]] ==
[[dur]] û [[dir]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:41, 31 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[nanoziko]] ==
[[nanoziko]] / [[nanzikî]] / [[nano ziko]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 06:57, 4 îlon 2020 (UTC)
{{rfk|nanozikî|r=324}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:27, 27 nîsan 2025 (UTC)
{{ds-kirî|nanozikî}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:07, 1 hezîran 2025 (UTC)
== [[sînor]] / [[sinor]] ==
sînor / sinor
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:15, 4 îlon 2020 (UTC)
{{rfk|sînor|r=446}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:25, 27 nîsan 2025 (UTC)
{{ds-kirî|sînor}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:08, 1 hezîran 2025 (UTC)
== [[ard]] ==
ard / ar -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:10, 6 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:52, 27 nîsan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} ard rasttir dibînim -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:10, 6 îlon 2020 (UTC)
{{şîrove}} "ar" tê maneya "agir"ê jî. "Erd" (rûyê zemînê) û "ard" jî wek telafûz nêzî hev in. Di peyva "arvan"ê de jî wek "ar" derbas dibe. Bi min wek "'''ar'''" baştir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:52, 27 nîsan 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[ajne]] /[[avjenî]]/[[ajnî]] ==
[[ajnî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:05, 6 îlon 2020 (UTC)
* {{rfk|ajne, avjenî|r=42, 50}} Di vê rêbera rastnivîsînê de her du formên wek "ajne" û "avjenî" wek standard hatine qeydkirin. Lewma formên peyva ''ajne'' yên wek "ajnê", "ajnî", "aşnê" divê li madeya "ajne"yê werin alîkirin, lê "avjenî" bi serê xwe cih bigire. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:39, 21 îlon 2024 (UTC)
{{ds-kirî|[[avjenî]], [[ajne]]}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:09, 1 hezîran 2025 (UTC)
== [[gurî]] ==
[[gurî]] / [[gurîtî]] / [[gurriyatî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:53, 6 îlon 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[parsî]] ==
[[partî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:09, 7 îlon 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[xwestin]] ==
xwestin / xwastin
xwestin rasttir dibînim. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 18:09, 9 îlon 2020 (UTC)
{{rfk|xwastin|r=523}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:48, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ez jî '''''[[xwestin]]''''' rasttir dibînim; jixwe '''''[[xwastin]]''''' bi min giran têt, o '''''[[xwestin]]''''' di hemî lehceyên kirmancî da heye. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 08:21, 19 îlon 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[îrrasyonel]] ==
[[îrrasyonal]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:44, 9 îlon 2020 (UTC)
{{rfk||}} Ev peyv derbas nabe, feqet peyva "rasyonel" wek standard cih girtîye (r. 403). Lewma ev jî dibe "'''îrrasyonel'''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:46, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[ajel]]/[[ajal]] ==
ajel / ajal
Em dibêjin heywan :) -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:11, 10 îlon 2020 (UTC)
* {{rfk|ajel|r=42}} --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:34, 22 îlon 2023 (UTC)
=== Gotûbêj ===
<!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Peyva ajel ji peyva sewal a kurmancî hatiye standin (s>J) sewal anku ajel ew canewerên çarpayî yên kedîkirî ne. Wekî pez, bizin, çêlek.{{bêîmze|46.161.196.225}}
{{ds-kirî|ajel}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:11, 1 hezîran 2025 (UTC)
== [[pîrebok]]/[[pîrhebok]] ==
{{rfk|pîrebok|r=365}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:44, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ji bo Rastnivîsnê Ferhanga Kurdî; di pera 365 wek ''pîrebok'' hatiye standardîzekirin. Di ferhengê de herweha ev form jî hene: pîraboçk, pîrabok, pîraboşk, pîrabotîk, pîravok, pîravtong, pîreboşk, pîrebotk.
Wîkîferhengê herwiha forma pîrhebok heye[[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 21:15, 10 îlon 2020 (UTC)
{{ds-kirî|pîrebok}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:11, 1 hezîran 2025 (UTC)
== [[şirove]] / [[şîrove]] ==
[[şirove]] / [[şîrove]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:50, 11 îlon 2020 (UTC)
{{rfk|şirove|r=466}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:41, 6 adar 2025 (UTC)
{{ds-kirî|şirove}}--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 08:48, 5 tîrmeh 2026 (UTC)
== [[pezkûvî]]/[[pezkovî]] ==
[[pezkovî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:59, 11 îlon 2020 (UTC)
{{rfk|pezkûvî|r=354}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:41, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{r|Balyozxane}} Yek hatîye jêbirin. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:22, 23 çiriya pêşîn 2020 (UTC)
{{ds-kirî|pezkûvî}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:13, 1 hezîran 2025 (UTC)
== [[dibistana seretayî]]/[[dibistana seratayî]] ==
[[dibistana seratayî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:22, 12 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:39, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min eşkere ye ku awayê standard "sereta" ye, lewma ya standard dibe "''[[dibistana seretayî]]''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:39, 6 adar 2025 (UTC)
{{ds-kirî|dibistana seretayî}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:13, 1 hezîran 2025 (UTC)
== [[sêzdeh]]/ [[sêzde]] ==
/ sêzde.
sêzdeh rasttir dibînim, sê + z + deh. Ti sedem nîne ji bo jêbirina "h" -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:34, 13 îlon 2020 (UTC)
{{rfk|sêzdeh|r=441}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:37, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ferhenga Michael L. Chyet (2003) sêzde ber alî dike sêzdeh. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:34, 13 îlon 2020 (UTC)
: yazdeh/yazde (beralîkirin), çardeh/çarde (dubare), pazdeh/pazde (beralîkirin), şazdeh/şazde (beralîkirin), hevdeh/hevde (beralîkirin), hejdeh/hejde (beralîkirin), nozdeh/nozde (beralîkirin) -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:37, 13 îlon 2020 (UTC)
{{ds-kirî|sêzdeh}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:14, 1 hezîran 2025 (UTC)
== [[çirûsk]]/[[çirîsk]] ==
çirîsk/çirûsk
çirûsk(m):1.kıvılcım, çıngı
çirîsk(m): binêre(çirûsk)(per 116,117)[[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 22:46, 13 îlon 2020 (UTC)
{{rfk|çirûsk|r=116}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:36, 6 adar 2025 (UTC)
{{ds-kirî|çirûsk}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:15, 1 hezîran 2025 (UTC)
== [[êriş]]/[[êrîş]] ==
[[êriş]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:11, 15 îlon 2020 (UTC)
* {{rfk|êriş|r=157}} --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:36, 22 îlon 2023 (UTC)
{{ds-kirî|êriş}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:15, 1 hezîran 2025 (UTC)
== [[mûmin]] ==
[[momîn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:22, 16 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:00, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ya standard "'''mumin'''" e. Bi apostrof baştir be jî, ji ber nestandardbûna emilandina apostrofan, ji bo halê standard bêapostrof baştir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:00, 5 sibat 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[xwarza]] ==
[[xwarza]] / [[xwarzî]] / [[xuharzî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:14, 17 îlon 2020 (UTC)
{{rfk|xwarza, xwarzî|r=523}} Her du peyv jî bi eynî maneyê di ferhengê de cih girtine. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:34, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[lênêrîn]] ==
[[nerîn]] / [[nêrîn]] / [[lênêrîn]] / [[lê nerîn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:19, 17 îlon 2020 (UTC)
{{rfk|nêrîn|r=333}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:04, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} "Lênêrîn" jixwe wek navdêr "''nêrîna li tiştekî''" ye, û "lê nêrîn" jî wek lêker "''li tiştekî nêrîn''" e, lê belê di her dukan de jî lêker/navdêr "nêrîn" e. "li"ya pêşî ji bo maneya "nêrîn"ê bêtesîr e/tesîreke wê lê nîne. Feqet ger were gotin ku "nêrîn" (bi îngilîzî "looking") wek forma "lênêrîn" xwedî maneyeke din e (ku divê ev yek jî bi çavkanîyan were îsbatkirin) wê gavê mirov kare wê peyvê cardin binirxîne. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:04, 5 sibat 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[hewar]] ==
[[hewar]]/[[hawar]] --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 10:07, 19 îlon 2020 (UTC)
{{rfk|hawar|r=190}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:35, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[muhakeme]] ==
[[muhakeme]] / [[mihakeme]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:59, 19 îlon 2020 (UTC)
{{rfk|mihakeme|r=309}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:34, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[tevabûn]] ==
[[tewabûn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:22, 19 îlon 2020 (UTC)
{{rfk|temambûn|r=477}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:32, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Tev jî formên "''temambûn''"ê ne. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:32, 5 sibat 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[fiêl]] ==
[[fiêl]] / [[fîil]] / [[fîîl]] Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:12, 19 îlon 2020 (UTC)
{{rfk|fêl|r=164}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:21, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[pêşniyazkirî]] ==
[[pêşniyarkirî]], [[pêşniyar]] beralî dike [[pêşniyaz]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:24, 19 îlon 2020 (UTC)
'''Eynî mijar li jêr hatîye vekirin. Ji bo standardkirinê binêrin ya jêr.''' -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:18, 5 sibat 2025 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[xwehr]] ==
[[xwehr]] / [[xwar]] Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:43, 20 îlon 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Bikarhêner}} Kîjan mane? Jixwe ya xwehr (îng crooked) standardkirî bû lê te xwehr kopî kir xwar. Çima? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:07, 20 îlon 2020 (UTC)
: {{r|Guherto}} Merheba bira. Li ser rûpela "[[xwar]]" maneya rengdêrî ya "ne rast, tewandî" tinebû. Tenê maneyeke wek "çewt, xelet" ''(bi cureyê rengdêrî)'' hebû. Ji ber ku mane kêm û şaş hatibû dayîn min ji rûpela "[[xwehr]]" mane û beşên din kopî kir û bi rûpela "[[xwar]]" ve kir. Û ji ber ku ji bo tenê maneyekê du awayên nivîsê yê wek "[[xwar]]" û "[[xwehr]]" wek standard cih digirtin, min şablona standardkirinê tevlî wan her du rûpelan jî kir, û ji bo ku em awayê standard bibijêrin li vir beşekê nû vekir. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:25, 20 îlon 2020 (UTC)
::Tu dikarîbû bnr-ek lê zêde bikê, ne hewce ye kopîkirin. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:48, 20 îlon 2020 (UTC)
::: {{r|Guherto}} Ji ber ku ez awayê standard wek "[[xwar]]" dibînim min rûpelê wek standard hazir kir. Awayê bnr-yî ne awayê wek standard e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:07, 20 îlon 2020 (UTC)
::::Temam. Lê hêjî kopîkirin ne hewce ye, bnr min çawa kir li [[#girover]] ji ber ku ez wê standard didîtim (dibînim). -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 10:11, 20 îlon 2020 (UTC)
::::: {{r|Guherto}} Baş e, di yên din de êdî ez ê wisa bikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:35, 20 îlon 2020 (UTC)
xwehr = ne rast (tirkî:eğik) û xwar = jêr (tirkî:aşağı).—<small>[[Bikarhêner:Gomada|<span style="background:Yellow">'''''<span style="font-family:Verdana;color:red">Gom<span style="color:#006400">ada</span></span></span>''''']]<sup>[[Gotûbêja bikarhêner:Gomada|peyam]]</sup></small> 15:34, 21 kanûna paşîn 2021 (UTC)
{{şirove}} Birêzan; li herêma me ev peyv wek ''xar'' tê bilêv kirin û li serhedê heta Sêwaz û ji wir hata Rojavayê Kurdistan bi vî awayîye.Min xwehr heya niha ji devê kesî nebihîstibû. Bi ya min xwar rasttire. Ji ber ku li devê meda di nav gellek peyvanda tîpa ''w'' nîne. Lê îjar jî bi peyva ''xwarin'' û ''xwarê''(berjêr) tevlihevî çêdibe.Dema ku standardizasyon çêdibe divê hemî peyvên watecûda bi wekhev neyê nivîsandin. Wek mînak niha peyvên kêr(alav,feyde,pozdewixî), ker(ajal,parçe,koma pez,nebihîz) 3-4 peyven ku xwendinên wan cûdane bi heman awayî tê nivîsin,ev zimanê me zortir dike û dibe sedema xeletîyan.Divê ziman standardîze be, lê ne bi vî awayî. {{bêîmze|Mihemed Qers}}
== [[dirist]] ==
[[dirist]] / [[dirust]]
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 13:44, 20 îlon 2020 (UTC)
{{rfk|dirust|r=137}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:15, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[neynok]] ==
[[neynok]] / [[neynûk]] / [[nînok]] / [[nênûk]] / [[nenîk]]
Awayê ku herî kêm tê bikaranîn (nînok) weke standard hatîye neqandin, ez vê şaş dibînim. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:43, 20 îlon 2020 (UTC)
Eynî rûpel li nînok û nênûk hene. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:45, 20 îlon 2020 (UTC)
{{rfk|nenûk|r=328}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:10, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi qenaeta min divê halê standard "neynûk" be. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:10, 5 sibat 2025 (UTC)
== [[neynokbir]] ==
neynokbir/neynûkbir/nenîkbir
Divê li gora encama [[#neynok]] werê standardkirin. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:39, 20 îlon 2020 (UTC)
{{rfk|nenûk|r=328}} Di vê ferhengê de ''nenûkbir'' nîne, lê ji bo peyva "neynûk"ê awayê standard wek "nenûk" hatîye nîşandayîn. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:12, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min wek halê standard forma "neynûk" baştir e. Lewma ev jî dibe "''neynûkbir''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:12, 5 sibat 2025 (UTC)
== [[xwijok]] ==
[[xişok]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 02:46, 21 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:44, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ji ber ku halê wê yê lêker wek "''xişîn''" e, ya standard "'''xişok'''" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:44, 5 sibat 2025 (UTC)
== [[pirhindik]] ==
[[pir hindik]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:34, 22 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:30, 3 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Formeke wek "pirhindik" bi min bêçavkanî ye. Lewma ya standard divê wek "pir hindik" be. Feqet peyva "pir hindik" jî bi maneya "teqrîben, nêzîkî" nîne. Divê ew qism jî were rastkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:30, 3 sibat 2025 (UTC)
:Min maneya wê fam kir, feqet nizanim, emilandineke wisa heye an tine. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:26, 6 adar 2025 (UTC)
== [[qireqir]] ==
[[qîreqîr]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:22, 23 îlon 2020 (UTC)
{{rfk|qîreqîr|r=390}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:11, 3 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[pênivîs]] ==
[[pênivîs]] / [[pênûs]] / [[pênivîsk]].
Pênivîs bêhtir tê bikaranîn. Ev û pênûs eynî peyv in, lê pênûs li gora "nûsîn"a soranî û pênivîs li gora "nivîsîn"a kurmancî hatîye çêkirin. Em wan bikin yek?
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:58, 24 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:34, 3 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
# pênivîs - Ferhenga Birûskî Michael L. Chyet
# pênivîs - Ferhenga Destî çap 2 Umîd Demîrhan
# pênivîs - Ferhenga Kurdî ( Kurmancî ) - Farsî. Koma Kurdîya Kurmancî. Azad MAKÛYÎ,Haşim MEHMÛDÎ,Şehnaz OMERZADE, Nasir ECEMÎ,Mohsin QULÎZADE,Kejal PADAŞ,Efsûn YÛSIFÎ
# pênivîsk - Rehnas Husein Muhammed
{{şîrove}} Qertafa "-k"yê di çêkirina navdêran de li kar e. Lewma ji alîyê xisûsîyeta navderbûnê ve "''pênivîsk''", ji alîyê pratîkbûnê ve ''"pênivîs"'' baştir e. Lê eger krîtera girêdayîmayîna bi eslê peyvê ve were esasgirtin, wê gavê jî ''"pênûs"'' baştir e. Biqenaeta min "'''pênivîsk'''".
: Ji xeynî vê qismeke ji [https://zimannas.wordpress.com/2015/12/21/em-cima-kurdi-li-ber-xwe-dikin-ciyayeki-ase/ nivîsa malpera Zimannasê]:
: ''[Peyva ”qelem” ji aliyê hemû kurdan ve û ji hêla miletên misilman ve ji Indonezyayê heta Mexribê tê fehmkirin û bikaranîn. Lê nivîskarên me qîma xwe bi wê naînin ji ber ku bi xeletî bawer dikin ku eslê wê erebî ye. Lê eslê wê jî yûnaniya kevn e: κάλαμος (kálamos). Li cihê wê nivîskarên me xwe dispêrin peyvên çêkirok yên wek ”pênivîs(k)” yan jî ”pênûs” ya ji soranî wergirtî. Lê ”pênivîs(k)” yan ”pênûs” her tişt e ku mirov pê dinivîse, ne tenê qelem e: li vê serdemê mirov zêdetir ne bi qeleman lê bi kompûterê, bi telefona mobîl yan bi hin makîneyên din dinivîse. Amûrên nivîsînê yên serekî niha ne qelem in lê kompûter, laptop, tablet û telefon in. Li gor vê rêbazê be, divê mirov ji kompûteran, ji laptopan re, ji tabletan re û ji telefonan re jî bibêje ”pênivîsk/pênûs”, ne tenê ji qeleman re!]'' -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:34, 3 sibat 2025 (UTC)
== [[behs]] ==
/ [[bas]].
Em dibêjin beḧs. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:49, 24 îlon 2020 (UTC)
{{rfk|behs|r=64}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:42, 1 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[dîmender]] ==
[[dîmender]] / [[dîmendêr]].
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:43, 25 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:45, 27 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Paşgira "der" / "dêr"ê li şûna hev hatine emilandin. Lê ji nimûneyên jêrîn ên ji ferhenga ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' tê dîtin ku qerareke li ser zelalkirina halê standard nehatîye dayîn di wê ferhengê de.
Bnr. [[agahîder]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 41), [[hander]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 189), [[kêfder]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 253),
[[avdêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû wek "avdêr" nîne, lê wek "''avdêrî''" r. 50), [[bangdêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 58), [[dengdêr]] ''-ne zelal e, nimûneyên din fahmbartir in-'' (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 127), [[kardêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 239), [[saloxdêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 425), [[serîhildêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 435),
[[xwepêşander]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne), [[dersder]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne), [[fermandêr]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de ev form nîne, wek "''fermande''" qeyd kirine), [[deynder]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne), [[girêdêr]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne), [[mizgînder]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne).
Bi min divê em li balê bidin ser ku paşgirên "der" / "dêr"ê eynî maneyê didin an na. Bi min paşgira "der"ê qismen nêzî paşgira "-dar"ê ye ku ew jî maneya xwedîbûna tiştekî dide. Lewma bi min ji bo peyvên bi maneya "kesê tiştekî dide" emilandina paşgira "-dêr"ê wê baştir be, lê ger em ji alîyê rehetîya telafûzê bifikirin jî paşgira "-der"ê baştir e ji bo awayê standard. Wek netîce bi min ji bo halê standard (ji bo peyvên bi maneya "kesê tiştekî dide") paşgira "-dêr"ê başttir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:45, 27 kanûna paşîn 2025 (UTC)
== [[Rojavayê Kurdistanê]] ==
/Rojavaya Kurdistanê.
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 12:16, 26 îlon 2020 (UTC)
[[Rojavayê Kurdistanê]]-- [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 21:12, 9 adar 2021 (UTC)
{{rfk|"rojava" wek nêr derbas dibe|r=416}} Peyva "rojava" wek nêr qeyd kirine. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:13, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} ''{{g|ku|rojava}}'' navdêreke mê ye, li gori rêzimana kurdi nivîsa rast "Rojava'''ya''' ''(îzafe)'' Kurdistan'''ê''' ''(çemandî)''" ye. Feqet "Rojavayê Kurdistanê" jî pirr tê bikaranîn.. --[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:27, 9 adar 2021 (UTC)
{{şîrove}} Slav @[[Bikarhêner:Ghybu|Ghybu]], gelle me û mezinên me dema ku peyvan bilêv dikin nêr/mê bûna peyvan çîye nizanin,peyv û hevokên ku hezaran salanda terkîb bûne û derketine holê bi xwezayî ji devên wan dertê, heta eger ku ji wanra nêr/mê bûna peyvan qal bikin yê li me xerîb binêrin,lê wan bi hezaran saland a bê qest û bi xwezayî vî zimanî qasê ku meriva şaş bihêle bi duzen ava kirine. Ne tenê herêma me, min ji gellê çi bihîstîye wê dinivîsim. Lê ji bilî gellê meyê hin herêmên Mêrdînê min tu cîyek nebihîstye ku mînak şûna ″porê min″ ″pora min″,yan jî şûna ″Rojavayê Kurdistanê″ ″Rojavaya Kurdistanê″ bêjin. Lê bi ya min em nikarin bêjin jiber ku peyva ″roj″ mê ye divê ″rojava″ jî mê be. Rast e rojava ji roj tê lê edî rojava peyveke cûda ye. [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 23:09, 9 adar 2021 (UTC)
{{şîrove}} Di Rêbera Rastnivîsînê ya Komxebata Kurmancîyê de (r.40) jî wek alî (wek hêl, ne parçe) "'''rojavayê''' Kurdistanê" derbas kirine. Ji ber ku eynî peyv e, em dibînin ku li wir jî wek "nêr" qebûl kirine. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:13, 26 kanûna paşîn 2025 (UTC)
== [[diz]] ==
[[diz]] / [[dizîker]] / [[dizîkar]]. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 12:37, 27 îlon 2020 (UTC)
{{rfk|diz, dizek|r=138}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:06, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} ″diz″ ji xwe kesek e ″dizî″ jî karesata wî/ê ye lewma ″dizîker/dizîkar″ xeletin.[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 20:38, 9 adar 2021 (UTC)
:Kes nabêje "dizîker,/dizîkar", kurd dibêjin : dizek . [[Taybet:Beşdarî/83.252.83.100|83.252.83.100]] 19:25, 3 gulan 2023 (UTC)
{{şîrove}} Şiroveyên hatî yên berê ji bo min jî rast dixuyin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:06, 26 kanûna paşîn 2025 (UTC)
== [[rêkxistin]] ==
[[rêxistin]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:03, 27 îlon 2020 (UTC)
*{{rfk|rêxistin}}--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:27, 23 îlon 2023 (UTC)
=== Gotûbêj ===
<!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} [[rêxistin]] -- [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 20:39, 9 adar 2021 (UTC)
== [[cihêl]] ==
[[cihêl]] / [[cehêl]] standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:38, 29 îlon 2020 (UTC)
{{rfk|cahil|r=94}} Forma standard wek "'''cahil'''" hatîye nîşandan, ji bo kelîmeya "cihêl". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:10, 4 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[tihn]] ==
[[tên]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:41, 30 îlon 2020 (UTC)
{{rfk|tîn|r=488}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:01, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[gişt]] ==
[[gişt]] / [[giş]] / [[gî]] / [[gîh]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:25, 30 îlon 2020 (UTC)
{{rfk|gişt|r=179}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:55, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[tal]] ==
[[tehl]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:35, 1 çiriya pêşîn 2020 (UTC)
{{rfk|tahl|r=472}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:51, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Birêzan pirranîya gellê me vê peyvê wek ″تعال″ bilêv dike lê alfabeya me da tîpa ″ع″ tune.Divê em jibo vê tîpê standartekî çêkin, wek şûna ″ع″ ″a yan jî eh″ bikarbînin wê demê pirsgirêk yê jibo peyvên din jî çareser be. -- [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 21:31, 9 adar 2021 (UTC)
== [[şuxl]] ==
[[şol]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 02:56, 2 çiriya pêşîn 2020 (UTC)
[[şuxil]]--[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 21:41, 9 adar 2021 (UTC)
{{rfk|şixul|r=466}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:50, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[gilîz]] ==
[[girêz]] / [[gilîz]] / [[gilêz]] -- [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:25, 5 çiriya pêşîn 2020 (UTC)
:girêz [[Taybet:Beşdarî/~2026-20662-33|~2026-20662-33]] ([[Gotûbêja bikarhêner:~2026-20662-33|talk]]) 19:33, 3 nîsan 2026 (UTC)
{{rfk|gilêz|r=176}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:46, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Çi wê ê standardkirin? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:27, 14 çiriya paşîn 2020 (UTC)
{{şîrove}} [[girêz]] -- [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 20:42, 9 adar 2021 (UTC)
== [[waris]] ==
/ [[wêris]]
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:03, 6 çiriya pêşîn 2020 (UTC)
{{rfk|waris|r=504}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:00, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[rêveber]] ==
/birêveber. Ber alî dike "birêveber" lê bahwer im "rêveber" bêhtir tê bikaranîn. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:50, 12 çiriya pêşîn 2020 (UTC)
{{rfk|birêveber|r=86}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:15, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min her çiqas lêker "''bi rê ve birin''" be jî halê standard wek "''rêveber''" be baştir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:15, 26 kanûna paşîn 2025 (UTC)
== [[jêk cuda bûn]] ==
/jê cuda bûn. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:15, 15 çiriya pêşîn 2020 (UTC)
{{rfk|ji hev cihê bûn, ji hev cuda bûn|r=230}} Forma "jêk" (ji yek) di vê ferhengê de derbas nabe. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:13, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} [[jê cuda bûn]]--[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 18:18, 7 adar 2021 (UTC)
{{şîrove}} Di "Cuda bûn" / "cihê bûn"ê de jixwe du tişt ji hev cuda dibin, lewma bi min madeyeke wek "''jê cuda bûn''" / "''jê cihê bûn''" (ji tiştekî cuda bûn / ji tiştekî cihê bûn) nehewce (neferhengî) ye. Forma "''jêk''" ''(ji yek)'' di ferhenga 'Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû' de derbas nabe.
Lewma bi dîtina min her du formên mewcûd jî nestandard in, lê belê ya standard "'''''ji hev cuda bûn'''''" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:13, 26 kanûna paşîn 2025 (UTC)
== [[pakêt]] ==
/paket. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:04, 24 çiriya pêşîn 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:56, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû de (r. 346) ji bo halê standard yê kelîmeya "''parsêl''"ê "''parsel''" nîşan dane. Ev jî wekî wî ye, '''lewma halê standard "''paket''" e''', lê ne "''pakêt''". Ji ber ku telafûza "''pakêt''"ê jî li gor yê din zehmettir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:56, 26 kanûna paşîn 2025 (UTC)
== [[hevaltî]] ==
/ [[hevalî]]. Hevaltî rasttir e û bêhtir tê bikaranîn. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:32, 31 çiriya pêşîn 2020 (UTC)
{{rfk|hevalî, hevaltî|r=200}} Di vê ferhengê de her du formên wek "''hevaltî''" û "''hevalî''" jî wek standard hatine qebûlkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:52, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min divê em tenê yekî ji wan wek ya standard bibijêrin. Ew jî divê '''''hevaltî''''' be. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:52, 26 kanûna paşîn 2025 (UTC)
== [[êzidî]] ==
[[êzîdî]]/[[êzdî]] - -[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:04, 11 çiriya paşîn 2020 (UTC)
{{rfk|êzidî|r=158}} û wek kelîmeya jêçêbûyî: "êzidîtî". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:48, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} [[êzdî]]--[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 18:16, 7 adar 2021 (UTC)
== [[potîn]] ==
[[putîn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:04, 14 çiriya paşîn 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:59, 19 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di pir cihan de wek "potîn" derbas bûye, lê min li ti derê forma "putîn" nedît. Lewma bi min eşkere ye ku halê standard wek "'''potîn'''" e. Û ger forma din bi temamî bêçavkanî be, em wê formê jî jê bibin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:59, 19 adar 2025 (UTC)
== [[diwazdeh]] ==
[[diwazdeh]] / [[dazde]]
{{rfk|duwazdeh|r=142}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:19, 4 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[xwende]] ==
[[xwenda]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:47, 25 çiriya paşîn 2020 (UTC)
{{rfk|xwende|r=524}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:17, 4 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ji bo kelîmeya "'''xwende'''" divê forma standard "xwende" be, ne ku "xwendî". Maneya kesê perwerdegirtî û kesê du-kelîme-xwendî divê ne eynî be. Lewma ez kelîmeya "xwende" pêşnîyar dikim ji bo awayê standard. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:17, 4 çiriya paşîn 2024 (UTC)
== [[pêşniyaz]] ==
[[pêşniyaz]] / [[pêşniyar]] / [[pêşniyaz kirin]] / [[pêşniyar kirin]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:06, 25 çiriya paşîn 2020 (UTC)
{{rfk|pêşnîyaz|r=359}} Kelîmeyên jêçêbûyî ên "pêşnîyaz kirin", "pêşnîyazî", "pêşnîyazkirin" jî wek standard hatine nîşandan. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:12, 4 çiriya paşîn 2024 (UTC)
:Dibe ku ev peyv pêşniyaz be ne pêşn(î)yaz. Ev peyv hîn bêtir nêzî hizir e. Lewra niyaz wateya daxwazê dide lê niyar wateya neyartiyê dide. Divê ev peyv pêşniyaz be. [[Bikarhêner:Mûhemmed Seîd|Mûhemmed Seîd]] ([[Gotûbêja bikarhêner:Mûhemmed Seîd|gotûbêj]]) 21:03, 28 tebax 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Balyozxane}} Wê çi ê standardkirin? Yan ti dixwazê ya standard werê guhertin? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:06, 25 çiriya paşîn 2020 (UTC)
:{{r|Guherto}} Nabînî pêşniyar dibêje pêşniyaz rast e pêşniyaz kirin dibêje pêşniyar kirin rast e. Kîjan rast e? --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:21, 25 çiriya paşîn 2020 (UTC)
::Qusûrê ne mêzêne, mi tenê rûpela pêşniyaz vekir û dît ku dibêjê pêşniyaz / pêşniyar. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:56, 25 çiriya paşîn 2020 (UTC)
== [[kamil]] ==
[[kamil]], [[kamîl]], [[kamul]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:02, 29 çiriya paşîn 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:49, 3 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ev peyveke Erebîye, lê dema ku Kurd wê bilêv dikin ″kamil″ dibêjin.Lewma ″kamil″ rasttire, ″kamîl û kamul″ bilêvkirinên tirk û hindîyane. [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 17:26, 7 adar 2021 (UTC)
{{şîrove}} Ya standard bi eşkereyî "kamil" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:49, 3 çiriya paşîn 2024 (UTC)
: Lê di Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî) - Tirkî ya Nû ya M. Malmîsanij û yên din de cih dane madeya "tekamil" û formên wê yên nestandard ên wek ''tekamîl'', ''tekamul''. Lewma ya standard li gor vê ferhengê dide "'''kamil'''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:54, 3 çiriya paşîn 2024 (UTC)
= Daxwazên standardkirinê: 2021 =
== [[fêrgeh]] ==
[[fêrgeh]] / [[fêrge]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:58, 5 kanûna paşîn 2021 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:36, 3 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} [[fêrgeh]] bê guman rasttire, ev tîpa ″h″ di gellek peyvanda li hin herêman nayê bilêvkirin.Lê ev nayê manê ku ″h″ tunne.
{{şîrove}} Herçend ev kelîme di Ferhenga Kurdî ya Rastnivîsînê ya M. Malmîsanij û yên din de derbas nabe jî, ji madeyên "azmûngeh" (r. 54), "dadgeh" (r. 120), "dangeh" (r. 122) û yên din tê famkirin ku ji bo paşgira "-geh"ê (formên din: "-ge") ya standard "-geh" e. Lewma em karin bêjin ku ji bo vê kelîmeyê divê ya standard bibe "fêrgeh". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:36, 3 çiriya paşîn 2024 (UTC)
== [[bêhêwî]] ==
[[bêhewî]]
Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:52, 23 kanûna paşîn 2021 (UTC)
{{rfk|hewî|r=202}} Ji bo maneya "kuma" awayê standard wekî "hewî" hatiye nîşandan. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:25, 3 îlon 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Peyvên "hewî" û "hêwî" her du jî xweyî çendîn maneyan in. Lewma ger beralîkirin were danîn divê maneya ku jê tê beralîkirin jî were nivîsîn. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:25, 3 îlon 2023 (UTC)
== [[pêşnûma]] ==
[[pêşnima]]
Divê bên standardkirin. Bi îxtîmalekî mezin ya standard "pêşnima" ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:20, 12 sibat 2021 (UTC)
'''Eynî mijar li jêr hatîye vekirin. Ji bo standardkirinê binêrin ya jêr.''' -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:15, 3 çiriya paşîn 2024 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
[[pêşnima]]--[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 18:13, 7 adar 2021 (UTC)
== [[jenosîd]] ==
[[jînoside]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:39, 13 sibat 2021 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:13, 3 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} "jînoside" an "jînosid" ? Min nivîsandinên formên "jînosideyê" an "jînosideya" pirr nedîtiye...--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:48, 13 sibat 2021 (UTC)
{{şîrove}} Bi min ya standard "[[jenosîd]]" e. Halên din ên "cenosîd", "jînosîd", "jînosid" kêm-li-kar in. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:13, 3 çiriya paşîn 2024 (UTC)
== [[kesaxtin]] ==
- [[kezaxtin]]
Standardkirin hewce ye. Bi ya min ya rast "kesaxtin" e, yê din xelet e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:57, 14 sibat 2021 (UTC)
{{rfk|kesaxtin|r=250}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:07, 3 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[derazîng]] ==
/ [[derazînk]]
Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:54, 19 sibat 2021 (UTC)
{{rfk|derazînk|r=127}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:53, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Bikarhêner}} bira navê sernavî neguherîne. Parametreya yekem ya şablona {{ş|ds}} divê sernavê vê gotûbêjê be.--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:51, 19 sibat 2021 (UTC)
== [[rovî]] ==
[[rûvî]]/[[rîvî]]. Bi ya min ji bo maneya "guhandarek" rovî zêdetir tê bikaranîn. Bi maneya "hinav" jî du rûpel ([[rûvî]]/[[rîvî]]) hene.--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:19, 19 adar 2021 (UTC)
{{rfk|rûvî|r=423}} Bi maneya "ince bağırsak". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:40, 30 çiriya pêşîn 2024 (UTC)
{{rfk|rovî|r=419}} Bi maneya "tilkî". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:41, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[esman]] ==
[[esman]] | [[asman]] | [[ezman]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:00, 24 adar 2021 (UTC)
{{rfk|esman|r=152}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:38, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ji ber ku ji didu peyvan pêk tê wek [[asê]]+[[man]]. Ezê peyva [[asman]] pêşniyar bikim. [[Bikarhêner:Key Mîrza|Key Mîrza]] ([[Gotûbêja bikarhêner:Key Mîrza|gotûbêj]]) 16:30, 10 gulan 2021 (UTC)
== [[niha]] ==
[[niha]] / [[niho]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:22, 25 adar 2021 (UTC)
{{rfk|niha|r=333}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:35, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[sûper]] ==
Standardkirinê teleb dikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:55, 26 adar 2021 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:27, 3 îlon 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Li gorî telefûzê ya standard "super" e, kes halê dirêjkirî nabêje, bi qasî zanîna min. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:48, 30 çiriya pêşîn 2024 (UTC)
== [[sûperlatîv]] ==
Standardkirinê teleb dikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:57, 26 adar 2021 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:28, 3 îlon 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi ya min ya rast "super" û "superlatîv" e, ji ber ku gava em bi lêv dikin em wek "superlatîv" bi lêv dikin, ne wek "sûperlatîv". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:57, 26 adar 2021 (UTC)
== [[radîkalîzm]] ==
/[[radikalîzm]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:21, 30 nîsan 2021 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:46, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Balyozxane}} Bi min peyveke wekî "radikalîzm" tine ye û bi xeletî wisa hatiye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:16, 17 tebax 2022 (UTC)
== [[texdîr]] ==
[[teqdîr]] standardkirin hewce ye. Bi min "teqdîr" standardtir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:52, 18 gulan 2021 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:45, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Pir eşkere ye ku ya standard "'''teqdîr'''" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:45, 30 çiriya pêşîn 2024 (UTC)
== [[tayîn]] ==
[[teyîn]] / [[tayîn]] / [[teîn]] / [[tayin]]. Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:30, 9 tîrmeh 2021 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:55, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Mirov di navbera "tayîn" û "tayin" de bêqerar dimîne. Lê bi min divê wek halê standard "'''tayîn'''" were dîyarkirin. Ji ber ku zêdetir li kar e (di lêgera Google Searche de jî zêdetir "tayîn" derdikeve, ''formata lêgerînê: wek "tayîna xwe" / "tayina xwe"''). -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:55, 29 çiriya pêşîn 2024 (UTC)
== [[nod]] ==
* [[not]]
Ev made li vir heye: [[Wîkîferheng:Daxwazên_standardkirinê#not,_nod]] Bi şaşî tekraren hatîye tevlîkirin. Ji bo agahîyan lê binêrin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:00, 29 çiriya pêşîn 2024 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Zanavano}}. Bnr. [[WF:Rêzikên standardkirinê]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:28, 1 çiriya pêşîn 2021 (UTC)
= Daxwazên standardkirinê: 2022 =
== [[xizar]] ==
[[xizar]], [[xîzar]]. Standardkirin hewce ye.{{bêîmze|Bikarhêner}}
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:46, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Forma xizar hema bêje li ti derê tine ye. Lewma halê standard divê "xîzar" be. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:46, 29 çiriya pêşîn 2024 (UTC)
== [[Nû Zelenda]] ==
[[Zelandaya Nû]] zêdetir tê bikaranîn.[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:18, 9 kanûna pêşîn 2022 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:39, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di '''Rêbera Rastnivîsînê''' ya Komxebata Kurmancîyê de (r. 37) wek "Zelandaya Nû" hatîye qeydkirin. Di kitêba '''Rastnuştişê''' ya Gruba Xebatê ya Vateyî de jî ev peyv wek "Zelandaya Newîye" (r. 67) hatîye qeydkirin. Lewma bi min divê halê standard bibe "Zelandaya Nû." -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:39, 29 çiriya pêşîn 2024 (UTC)
= Daxwazên standardkirinê: 2023 =
== [[pirzik]] ==
[[pizik]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:37, 15 kanûna paşîn 2023 (UTC)
* {{rfk|pizrik}} --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:37, 15 kanûna paşîn 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min forma wek "{{g|ku|pizrik}}" jî heye û divê ew jî were standardkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:53, 31 tebax 2023 (UTC)
== [[bûro]] ==
{{g|ku|bûro}} / {{g|ku|buro}} / {{g|ku|buriyo}} standardkirin hewce ye. --- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:06, 22 kanûna paşîn 2023 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:58, 31 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di ''Rêbera Rastnivîsînê'' ya Komxebata Kurmancî de di rûpela 139em de nimûneyeke wek "'''buroya wî'''" hatiye dayîn. Ji vê nimûneyê mirov dikare bêje ku ji bo vê peyvê awayê standard wekî "buro" dibînin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:53, 3 îlon 2023 (UTC)
== [[fezîlet]] ==
[[fazîlet]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:47, 26 kanûna paşîn 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ev peyv jî ji erebî tê. Herçiqas di hin peyvên reh-erebî de (wek nimûne "edalet") ji bo eyna erebî "e" hatibe tercîhkirin jî bi min ji bo em li vir nêzî telafûza di pratîkê de bibin, forma bi "e"yê neyê tercîhkirin jî dibe. Lewma ez "fazîlet"ê pêşnîyar dikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:57, 29 çiriya pêşîn 2024 (UTC)
== [[Uman]] ==
{{g|ku|Uman}}, {{g|ku|Oman}}, {{g|ku|Iman}}, {{g|ku|'Iman}}, {{g|ku|'Uman}}, {{g|ku|Umman}}
Standardkirin hewce ye.
{{rfk|}}
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di '''''Rêbera Rastnivîsînê''''' ya Komxebata Kurmancîyê de awayê "'''Uman'''" wekî standard hatiye nîşandan. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:34, 3 nîsan 2023 (UTC)
== [[hejî]] ==
{{g|ku|hejî}}, {{g|ku|hêja}}
Standardkirin hewce ye.
{{rfk|}}
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Ferhengvan}} Bi min ev herdu, wekî formên eynî peyvê dixuyin. Lewma standardkirinê teleb dikim. Her çiqas berçav be ku awayê stadard "hêja" ye jî, li vir teleb dikim. Lê ger ne formên eynî peyvê bin, xêra xwe li vir binivîsin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:52, 7 nîsan 2023 (UTC)
::{{Silav|Bikarhêner}} Rast dibêjî, "hejî" formeke devokî (behdînî) ya peyva "hêja" ye. --[[Bikarhêner:Ferhengvan|Ferhengvan]] ([[Gotûbêja bikarhêner:Ferhengvan|gotûbêj]]) 20:31, 7 nîsan 2023 (UTC)
== [[sererast]] ==
{{q|ku|sererast}}, {{q|ku|serrast}} Standardkirin hewce ye. Bi min xuya ye ku forma standard "sererast" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:02, 25 nîsan 2023 (UTC)
{{rfk|serrast, serrast kirin|r=437}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:41, 31 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bila piştî gihîştina netîceyê, standardkirina formên din ên wekî "sererastkirin" / "serrastkirin", "sererast kirin" / "serrast kirin" jî neyê jibîrkirin. Bi min divê forma standard bibe "sererast". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:41, 31 tebax 2023 (UTC)
== [[fiqih]] ==
{{g|ku|fiqh}} / {{g|ku|fiqih}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:16, 30 nîsan 2023 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:16, 30 nîsan 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di ''Rêbera Rastnivîsînê'' ya Komxebata Kurmanciyê de (r.52, 53) behsa vê mijarê kirine: Bi sernavê "'''Nivîsîna hin bêjeyên bîyanî yên yekkîteyî ku di kurmancîyê da dibin dukîteyî'''", wek nimûne jî "'''aqil ✓''', '''aql ✗'''", "fikir ✓, fikr ✗", "qebir ✓, qebr ✗", "qebiz ✓, qebz ✗", "qedir ✓, qedr ✗", "qehir ✓, qehr ✗"...
Li binî jÎ dibêjin divê li gel îzafeyê [i] bê parastin, wekî "aqilê min ✓, aqlê min ✗", "fikira min ✓, fikra min ✗", "qebira min ✓, qebra min ✗"...
Lewma bi dîtina min, li gor vê rêberê divê wekî "fiqih" bibe ya standard. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:38, 31 tebax 2023 (UTC)
== [[îqaz]] ==
{{g|ku|îqaz}} - {{g|ku|îkaz}}. Standardkirin hewce ye. Piştî qerardana li ser forma standard, divê peyvên girêdayî wan jî yên wek "îqazker", "îqaz kirin" hwd jî bên guherandin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:14, 19 gulan 2023 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:14, 19 gulan 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di ''Rêbera Rastnivîsînê'' ya Komxebata Kurmanciyê de (r. 80) dibêjin "'''[q]ya bêjeyên biyanî neke [k]'''". "k"ya vê peyvê jî bi eslê xwe "[q]" ye, û lewma divê awayê standard bibe "îqaz". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:22, 31 tebax 2023 (UTC)
== [[hundirîn]] ==
{{g|ku|hindirîn}}, {{g|ku|hundirîn}} standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:41, 27 gulan 2023 (UTC)
{{rfk|hundir|r=217}} Ji ber ku awayê "hundir" wek standard hatiye bijartin, li gor vê "hundirîn" jî dibe awayê standard. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:40, 29 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[wasita]] ==
{{g|ku|wasita}}, {{g|ku|wesite}}, {{g|ku|wasîte}}, {{g|ku|wasta}}, {{g|ku|wesita}}, {{g|en|wesita}}, {{g|en|wesîte}}, {{g|en|wasite}}
standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:06, 21 hezîran 2023 (UTC)
{{rfk|wasîte|r=504}}
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[qebe]] ==
{{g|ku|qebe}} / {{g|ku|qeba}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:10, 27 tebax 2023 (UTC)
{{rfk|}} --[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:35, 29 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min forma "qeba" baştir e ji bo halê standard. Helbet gava dîyarkirina halê standand divê em halên emilandinê û peyvên jêçêbûyî jî bifikirin. Ji bo vê peyvê wek "qebetî" / "qebatî". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:26, 29 çiriya pêşîn 2024 (UTC)
== [[sewgûr]] ==
{{g|ku|sewgur}} / {{g|en|sewgûr}} / {{g|en|sewgir}} / {{g|en|sewgor}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:33, 29 tebax 2023 (UTC)
{{rfk|sewgurî, sewgura sibê|r=439}} Di ferhengê de wek "sewgurî" û wek "sewgura sibê" hatiye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:33, 29 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[Danmarka]] ==
[[Danîmarka]] zêdetir tê bikaranîn--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:54, 3 îlon 2023 (UTC)
{{rfk|}} Di vê ferhengê de peyvên "danîmarkî" û "danîmarqî" ber bi rûpela "danmarkî" ve yê hatine alîkirin. Lewma mirov fam dike ku navê vî welatî li gorî vê ferhengê "'''Danmarka'''" ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:12, 3 îlon 2023 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] Bi min ew mantiqeke şaş e. Loma em li ferhengên din jî binêrin baştir be. Ez fêm nakim çawa ferhengeke rastnivîsînê navên welatan qeyd nekirine. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:27, 3 îlon 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi ya ''Rêbera Rastnivîsînê'' ya Komxebata Kurmancî jî awayê standard "'''Danîmarka'''" ye (r. 31). Ez bixwe jî ji bo awayê standard "Danîmarka"yê pêşniyar dikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:55, 3 îlon 2023 (UTC)
== [[dijmîn]] ==
{{g|ku|dijmîn}}, {{g|ku|dijûn}} Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:57, 12 îlon 2023 (UTC)
{{rfk|dijûn|r=135}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:04, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[fîlmografî]] ==
[[filmografî]]
:--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:38, 20 îlon 2023 (UTC)
{{rfk|fîlm|r=166}} Şiklê standard ji bo peyva "film / fîlm / fîlim" wek "fîlm" hatîye nîşandan. Lewma li gor vê awayê standard dibe "'''fîlmografî'''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:07, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[not]], [[nod]] ==
[[not]], [[nod]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:23, 11 çiriya pêşîn 2023 (UTC)
{{rfk|nod|r=338}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:09, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[afrîkî]] ==
[[afrîkî]] / [[afrîkayî]] ?--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:06, 9 çiriya paşîn 2023 (UTC)
{{rfk|afrîkayî|r=41}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:11, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[eware]] ==
[[eware]]/[[aware]]
:Bi maneya ''avare/aylak/serseri''
{{rfk|153}}
:Ji bo maneya ''penaber'' di vê ferhengê de standardek tine ye. Loma divê du rûpel hebe ji bo kelîmeya kurmancî ya eware û yekî din ji bo maneya penaber.
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:14, 8 sibat 2024 (UTC)
=== Gotûbêj ===
<!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di ferhenga ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de jixwe standarda ji bo vê peyvê heye. Ji bo maneya penaber jî hebûna vê maneyê bi min bişibhe ye, wekî din ''ger hebe jî'' ji ber ku ji berhirefkirina maneya "''yê derbeder û bê cih û mal''" hatîye çêkirin, dîsa girêdayî wê maneya eslî ya derbiderîyê ye, lewma divê li ser rûpelekî werin standardkirin û ew rûpel jî pêşnîyara di kitêba nav-zikirkirî de be baştir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:22, 18 hezîran 2024 (UTC)
== [[wesetî]] ==
[[wasatî]] / [[wesetî]] standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:48, 29 adar 2024 (UTC)
{{rfk|wesetî|r=506}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:27, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[se direwît, karwan bi rêka xwe diçît]] ==
{{g|ku|se direwît, karwan bi rêka xwe diçît}} / {{g|ku|se direwin, karwan diborin}} / {{g|ku|seh direye kerwan dibihore}} Divê ev her sê form werin standardkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:51, 16 hezîran 2024 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:26, 7 tîrmeh 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ya standardtir "'''seg direye karwan dibihure'''" ye ("seg", ''Ji Bo Rastnivîsînê Ferhenga Kurdî (Kurmancî) - Tirkî ya Nû'', r. 429; "reyîn", r. 410; "karwan", r. 240; "bihurîn", r. 83). Wekî form (di qismê "Herwiha" de) em "'''bi ewtîna seyan, ji rê namîne karwan'''" jî nîşan bidin. Lê ji alîyê xwedîçavkanîbûnê ve jî divê werin kontrolkirin. Min ji bo "''bi ewtîna seyan, ji rê namîne karwan''" çavkanî peyda nekir, min ji bo "''seg direye karwan dibihure''" jî tenê çavkanîyekê peyda kir ([https://www.tirejafrin.com/pdf/Pend%20.pdf ''Pend û Peng''], Bîlal Hesen, di kitêbê de wek "seh direye kerwan dibihore" derbas dibe). -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:26, 7 tîrmeh 2024 (UTC)
== [[serobino bûn]] ==
{{g|ku|serobino bûn}} / {{g|ku|serûbin bûn}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:21, 14 tebax 2024 (UTC)
{{rfk|serûbin|r=438}} "serûbin bûn, serûbin kirin, serûbinbûn, serûbinkirin, serûbinkirî" wek peyvên jêçêbûyî hatine nîşandayîn. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:19, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[sanî]] ==
[[sanî]] / [[saniye]]
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:45, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ez dibêjim ber saniye be. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:22, 1 îlon 2024 (UTC)
{{şîrove}} Ez dibêjim bila "sanîye" be. Em wek sanîye telafûz dikin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:45, 29 çiriya pêşîn 2024 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] "Sanîye" çênabe. Li vir em "îy" nanivîsînin. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 13:34, 10 çiriya paşîn 2024 (UTC)
== [[pêşnûma]] ==
[[pêşnûma]] / [[pêşnima]] Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:00, 29 çiriya pêşîn 2024 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:00, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min divê "pêşnima" be. Formên bi "u"yê zahfê caran nayên emilandin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:00, 29 çiriya pêşîn 2024 (UTC)
== [[payîz]] ==
[[payîz]] / [[payiz]]
Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:12, 29 çiriya pêşîn 2024 (UTC)
{{rfk|payîz|347}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:12, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[behîv]] ==
{{g|ku|behîv}} / {{g|ku|bahîv}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:41, 26 kanûna paşîn 2025 (UTC)
{{rfk|behîv|63}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:38, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[mitîng]] ==
[[mitîng]] / [[mîtîng]] Standardkirin hewce ye. -- --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 21:47, 27 kanûna paşîn 2025 (UTC)
{{rfk|mîtîng|r=316}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:43, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[rûtîn]] ==
[[rûtîn]], [[rutîn]] Standardkirin hewce ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:47, 18 sibat 2025 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:42, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şirove}} -->
{{şirove}} Bi min eşkere ye ku ya standard "'''rutîn'''" e. Peyvên telafûza wan dirêj be, bi "û"yê tên gotin, lê li vir dirêj nayê gotin. Lewma bi "u"ya bêkumik çêtir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:42, 1 hezîran 2025 (UTC)
== [[parleman]] ==
[[parleman]] / [[perleman]] / [[parlament]]/[[parlemento]] / [[perlemen]], Standardkirin hewce ye.--[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 00:53, 22 sibat 2025 (UTC)
{{rfk|parlamento|r=345}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:39, 1 hezîran 2025 (UTC)
=== Gotûbêj ===
{{şîrove}} Di ferhenga "Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû" da bi maneya ''meclis''ê wek "parlamento" hatîye qeydkirin, bi maneya ''azayê meclisê'' wek "parlamenter" hatîye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:39, 1 hezîran 2025 (UTC)
== [[sond]] ==
[[sond]], [[sûnd]]—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:41, 21 adar 2025 (UTC)
{{rfk|sond|r=447}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:35, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şirove}} -->
{{şirove}}
== [[sifet]] / [[sîfet]] ==
{{rfk|}}--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:41, 1 gulan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi dîtina min wek "sifet" zêdetir li kar e. (Ger rast transkrîbe kiribin) Di eserên berê yên kurdî de wek "sîfet" hatine gotin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:29, 31 gulan 2025 (UTC)
== [[mane]] ==
[[mane]], [[mana]]—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:13, 30 gulan 2025 (UTC)
{{rfk|mane|r=297}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:20, 31 gulan 2025 (UTC)
=== Gotûbêj ===
{{şirove}} {{r|Bikarhêner}} [https://ferheng.wmezopotamyaye.org/ weqfa mezopotamyaye] dibêje forma standard "mana" ye. Dikarî ji ferhenga rastnivîsînê binêrî? --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:18, 30 gulan 2025 (UTC)
: {{r|Wikihez}} Di Ferhenga Rastnivîsînê de wek "mane" (jê "mane dan", "manedan", "manedar", "manedarî") hatîye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:20, 31 gulan 2025 (UTC)
== [[memnû]] ==
{{g|ku|memnû}} {{g|ku|memnûh}} Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:34, 1 hezîran 2025 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:34, 1 hezîran 2025 (UTC)
=== Gotûbêj=== <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Eşkere ye ku ya standard "'''memnû'''" ye. Forma "memnûh" ne li kar e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:34, 1 hezîran 2025 (UTC)
== [[niha]] ==
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{rfk|niha|r=333}} [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 11:22, 16 sibat 2026 (UTC)
== [[gîya]] ==
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{rfk|gîya|r=180}} [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 11:24, 16 sibat 2026 (UTC)
Kelîmeya jêçêbûyî: {{g|ku|gîyagezk}} (mê, mane "''ısırgan otu''") -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:11, 5 tîrmeh 2026 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]], @[[Bikarhêner:Rewiye tine|Rewiye tine]] Bi ya min, ev ne meseleya standardkirinê ye lê ya îmlayê ye çûnkî ev problema îy - iy zêde tevlihev e. Dengdanek hewce ye ku bibêje konsensusek heye li ser bikaranîna -îy û ne -iy. Lê dengdanek wisa dê têk biçe ji ber ku hejmara kesên -îy tercih dikin kêmtir e li ser Wikiferhengê. Loma divê ev gotûbêj wek çênebû were tayînkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:04, 5 tîrmeh 2026 (UTC)
::Bi qenaeta min jî "'''îy'''" rasttir e. Divê em di demên pêş da wî meseleyê hel bikin. Feqet wekî ku te jî got, ji ber ku vê gavê li ser vê qerar nehatîye dayîn, em karin wek xwe bihêlin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:45, 6 tîrmeh 2026 (UTC)
== [[hincet]] ==
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{rfk|hincet|r=210}} [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 11:18, 16 sibat 2026 (UTC)
== [[kurmî]] ==
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{rfk|kurm (navdêr), kurmî (rengdêr)|r=269}} [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 08:55, 23 adar 2026 (UTC)
:kurmî berbelav e hem jî standardîze bûye bi min. Hem em dibêjin kurm bi gelemperî kirm kêm e. [[Bikarhêner:Bibliophile|Bibliophile]] ([[Gotûbêja bikarhêner:Bibliophile|gotûbêj]]) 10:55, 23 adar 2026 (UTC)
::Lê balkêş e ku rehê xwe yê qedîm zêdetir wekî [[kirm]] xuya dike. Lê etîmolojî nikare bikeve pêşberî bikaranîna giştî peyvekê ji aliyê xelkê ve. Helbet ev fikra min a şexsî ye. Ji bo selameta ferhengê kurdî ev yek gerek e bête çareserkirin. [[Bikarhêner:Bibliophile|Bibliophile]] ([[Gotûbêja bikarhêner:Bibliophile|gotûbêj]]) 11:01, 23 adar 2026 (UTC)
== [[tihn]] ==
[[tihn]], [[tîn]], [[tên]]
{{rfk|tîn (mê, mane: ''ısı; hararet, sıcaklık'')|r=488}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:18, 5 tîrmeh 2026 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
Ez bawer dikim ku ''tîn'' zêdetir tê bikaranîn.
== [[nehwirandin]] ==
[[nehwirandin]], [[norandin]]
bnr [[Gotûbêj:norandin]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:52, 3 tîrmeh 2026 (UTC)
{{rfk|nehwirandin|r=328}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:05, 5 tîrmeh 2026 (UTC)
=== Gotûbêj ===
== [[rus]], [[rûsî]] ==
[[rus]], [[rûs]], [[rûsî]], [[rusî]]
bnr. [[Gotûbêja kategoriyê:Rusî]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 07:11, 5 tîrmeh 2026 (UTC)
{{rfk|rûs, rûsî|r=422}} "rûs" wek navê miletê, "rûsî" wek navê zimanê hatîye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:57, 5 tîrmeh 2026 (UTC)
=== Gotûbêj ===
== [[dûkel]] ==
{{g|ku|dûkel}} / {{g|ku|dûkêl}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:14, 19 tîrmeh 2026 (UTC)
{{rfk|dûkel (nêr)|r=143}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:16, 19 tîrmeh 2026 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
okseh065rsad82825b0sbqhjj5ozkbf
6535040
6535039
2026-09-21T07:08:27Z
Kurdistanov
15290
/* ard */
6535040
wikitext
text/x-wiki
{{/destpêk}}
= Daxwazên standardkirinê: 2020 =
== [[mizhar]] ==
mizhar / miz'ar / mizar --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:31, 10 tebax 2020 (UTC)
===Gotûbêj===
{{şîrove}}
Min gelek ferhangan nêrî lê min kelîmeyek wisa nedît. Divê ev kelîme were peyitandin. Wexta em kelîmeyek bo standartkirinê hilbijêrin divê hinek ser bisekinin. Ka kelîme hêjaye an na. Kelîmeyê zêde tên bikaranîn divê were hilbijêrîn.--[[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 20:54, 3 çiriya paşîn 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[kîte]] / [[kite]] ==
[[kîte]] / [[kite]] Divê werin standardkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:11, 13 tebax 2020 (UTC)
{{rfk||}} Di vê ferhengê de "kîte / kite" nîne, lê wek lêker cih dane "kîte kirin"ê. Jê tê famkirin ku halê standard wek "'''kîte'''" dibînin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:31, 28 nîsan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ji bo '''''[[kit]]''''' ez dibêjim '''''[[kit]]'''''; nabêjim '''''[[kît]]'''''. Ma '''''[[kîte]]''''' ('''''[[kît]]''''') li kû derê li kar e? -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 15:13, 5 îlon 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[hêvişandin]] ==
[[hêvişandin]] / [[êvişandin]] / [[hewişandin]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:50, 15 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[hindirandin]] ==
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 09:09, 16 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
*Bi ya min divê forma ''hundirandin'' bibe standard. Li herêma Behdînan gelek caran '''u''' dibe '''i''', '''û''' jî dibe '''î'''. Loma [[hindir]] weke rûpela sereke hatiye çêkirin.—<small>[[Bikarhêner:Gomada|<span style="background:Yellow">'''''<span style="font-family:Verdana;color:red">Gom<span style="color:#006400">ada</span></span></span>''''']]<sup>[[Gotûbêja bikarhêner:Gomada|peyam]]</sup></small> 15:40, 21 kanûna paşîn 2021 (UTC)
** {{silav|Gomada}}. Bnr. [[WF:Rêzikên standardkirinê]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:42, 21 kanûna paşîn 2021 (UTC)
== [[zingar]] ==
Eynî rûpel li [[zeng]] û [[jeng]] heye. Ez awayê rast wek [[zingar]] dibînim. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 12:34, 18 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[axaftin]] ==
[[axaftin]] / [[axivtin]] / [[axiftin]] / [[axivîn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:23, 19 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Axiftin û axivîn ne eynî tişt in. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:05, 22 tebax 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[jêbirk]] ==
[[jêbir]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:41, 19 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Xuya dike paşgira -k (bi vê maneyê) gelekî nayê bikaranîn. Agirkuj bi dêla agirkuj-k tê bikaranîn, min tenê "jêbir" bihîstîye, "xwêdan-k" bêçavkanî ye, "demjimêr-k" kêm peyda dibê (bêhtir di spam de heye) ... -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:39, 22 tebax 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[bager]] ==
[[bager]] / [[bagêr]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:16, 19 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[çikilandin]] ==
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:52, 20 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[evraz]] ==
[[evraz]] / [[hevraz]] / [[evrazî]] / [[hevrazî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:03, 20 tebax 2020 (UTC)
:ervaz [[Taybet:Beşdarî/~2026-16469-22|~2026-16469-22]] ([[Gotûbêja bikarhêner:~2026-16469-22|talk]]) 19:30, 15 adar 2026 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[xwişk]] ==
[[xwişk]] / [[xweng]] / [[xwek]] / [[xuşk]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:33, 21 tebax 2020 (UTC)
{{rfk|xwişk, xweng, xweh|r=524 (xweng, xweh), 526 (xwişk)}}
Di ferhengê de "xwişk (r. 526), xweng (r. 524), xweh
(r.524)" tev jî bi maneya "''kız kardeş''" hatine qeydkirin. "xwek" û "xuşk" jî tê de nîne. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:04, 2 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} ''[[xuşk]]'' u ''[[xweh]]'' ji ber ku di devoka min da herdu jî têne bikaranîn, wek ''[[xuçik]]'' u ''[[xweng]]''. ''[[xwek]]'' devokî ye u bipiranî li rojavayê tê bikaranîn. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 15:49, 21 tebax 2020 (UTC)
: {{silav|Hakîmo}} Kengî dengdana li ser [[Wîkîferheng:Rêzikên standardkirinê|Rêzikên standardkirinê]] xilas bû, em ê dest bi standardkirinê bikin. Tu jî dikarî fikrên xwe li vir bibêjî '''[[Wîkîferheng:Dengdan/Rêzik/Rêzikên standardkirinê]]'''.
::Di devoka min de xwîşk, xuh û xwak hene. Di wextên cuda de tên bikaranîn. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 16:06, 21 tebax 2020 (UTC)
Ez awayê rast ya xwîşk/xwişk weke "xwîşk" dibînim, û di înternetê de bêhtir encam hene (hejmara rûpelan binêrin, hejmara encaman şaş e.) -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:27, 25 tebax 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin.
{{erê}}: [[xuşk]] u [[xweh]] (belkî [[xuçik]] jî, lê niha [[xuşk]] ancax têr dikê)-->
:Silav hevalên hêja, ez nizanim çawa deng bidim, lê ji bo standartkirinê divê mirov binere kîjan peyv kevintir e, ji bo vê yekê ez dengê xwe didim peyva '''xweh''' [[Bikarhêner:Kurdistanov|Kurdistanov]] ([[Gotûbêja bikarhêner:Kurdistanov|gotûbêj]]) 06:59, 21 îlon 2026 (UTC)
== [[xwendevan]] ==
[[xwendevan]] / [[xwandevan]] / [[xwêndkarî]] / [[xwîndkarî]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:50, 21 tebax 2020 (UTC)
{{rfk|xwendevan, xwendekar|r=524}} Di ferhengê de bi maneyên cuda her du peyv jî derbas dibin. "xwendekar" bi maneya "öğrenci, talebe"; "xwendevan" bi maneya "okuyucu, okur" hatîye qeydkirin. Lewma divê her du jî hebin. "''xwêndkarî''" û "''xwîndkarî''" jî formên "xwendekarî"yê ne, lewma divê wek "xwendekarî" werin standardkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:52, 2 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Xwendevan û xwendekar bi eynî maneyî ne lê ne eynî peyv in, nikarin weke yek werin standardkirin. Ji ber ku xwendin/xwandin weke xwendin hatîye standardkirin divê xwendevan jî wilo bibê. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:05, 4 îlon 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[sefhe]] ==
[[sefhe]] / [[seyfe]] / [[sayfe]] -- [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:34, 22 tebax 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:06, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ya standard "sehîfe" ye. Forma "sefhe" di şiîrên Ehmedê Xanî de derbas dibe, lê îro ev peyv bi forma "sehîfe" tê zanîn. Lewma bi min divê em awayê standard wek "'''sehîfe'''" nîşan bidin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:06, 1 hezîran 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[pergala xebatê]] ==
[[pergala xebatê]] / [[pergala xebitandinê]]
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:29, 26 tebax 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:49, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ya rast ev ne pirseke li ser standardkirinê ye. Lewma li vir neyê minaqeşekirin baştir e. Lê dîsa fikrê xwe bêjim. Îngilîzîya wî "''operating system''" e, ew jî bi kurdî him dibe "''sîstema xebitandinê''" him jî "''sîstema xebitînê''", ji ber ku lêkera "operate" him transîtîv e him jî întransîtîv. Bi min "pergala xebatê" jî ne xelet e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:49, 1 hezîran 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[qet]] ==
Navdêra qet/qed (bi maneya parçe).
Ji erebî hatîye û ya erebî "qet" e (bi t-ya qalin weke '''t'''ewandin). Ya kurdî bi t-ya normal e. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 18:24, 28 tebax 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:02, 28 nîsan 2025 (UTC)
Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de 'qed' derbas nabe, qet bi maneya 'hîç' derbas dibe, halê standard ya 'qet'a bi maneya 'parçe'yê wekî 'qut' nîşan dane. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:07, 28 nîsan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
* Hiii, min got yanî ya hoker bû. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 14:03, 29 tebax 2020 (UTC)
:{{r|Hakîmo}} Min şablonê li navdêrê danîbû lê baştir e ez binivîsim ku li ser navdêrê ye. (Min vêce nivîsî). -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:30, 29 tebax 2020 (UTC)
{{şîrove}} Wek min li jor nivîsî, ev peyv di ferhenga ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de tine ye, feqet bi min divê forma "'''qet'''" wek ya standard were qebûlkirin. Wek notek biçûk vê jî bêjim, ji "qet"a bi maneya "parçe" lêkera "qet kirin"ê jî çêbûye û lêkera "qut kirin"ê ku tê maneya "du tiştan ji hev qet kirin" û lêkera "qet kirin"ê bi eynî maneyê ne. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:02, 28 nîsan 2025 (UTC)
{{şîrove}} Li ser meseleya 'qet' û 'qut'ê jî, her du jî pir berbelav in, lewma bi min divê her du jî bimînin. Wekî din hin ferq jî hene di navbera wan de, wek nimûne, halê navdêrî ya "qet"ê berbelav e, lê halê navdêrî ya "qut"ê neberbelav e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:07, 28 nîsan 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[darberû]] / [[darberî]] ==
[[darberû]] / [[darberî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:36, 30 tebax 2020 (UTC)
{{rfk|berû|r=73}} Peyva "darberû" nîne, lê "berû" heye. Jixwe peyda "dar" standard e, nelihevî li ser qismê "berû" û "berî"yê ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:24, 27 nîsan 2025 (UTC)
{{ds-kirî|darberû}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:06, 1 hezîran 2025 (UTC)
== [[diran]] / [[didan]] ==
[[diran]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:52, 31 tebax 2020 (UTC)
{{rfk|diran|r=136}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:20, 27 nîsan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} '''''[[didan]]''''' awayê standard e ji ber ku '''''[[diran]]''''' ji wê hatiye: '''''didan''''' > '''''diran'''''
* Li Qonyayê herdu li kar in (ez '''''[[didan]]''''' bi kar tînim); li rojhilatê '''''[[didan]]''''' pirtir li kar e; Ehmedê Xanî di ''Nûbihara Biçûkan'' da '''''[[didan]]''''' bi kar anîbû. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 08:29, 1 îlon 2020 (UTC)
* {{erê}} [[didan]] -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 08:29, 1 îlon 2020 (UTC)
Em dibêjin diran û dinan. "Didan" formeke lêkerê ye jî ("ew xwarin ''didan'' xelkê"), baştir e ne weke hev bin.
Diran (înternetê bigere "diranê" û "diranên") gelekî bêhtir ji "didan" ("didanê", "didanên") tê bikaranîn.
Fikirên [[Bikarhêner:Ferhengvan|Ferhengvan]] (23ê îlonê 2019) li vir hene: [[Gotûbêja pêvekê:fînî-kurdî]]. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:02, 1 îlon 2020 (UTC)
{{şîrove}} {{r|Guherto|Hakîmo}} Hevalno hûn tim behsa bilêvkirinê dikim lê hûn ti carî behsa çavkaniyan nakin. Binêrin [[Wîkîferheng:Dengdan/Rêzik/Rêzikên standardkirinê]] dibêje ''Derbarê awayê peyvekê de çiqas zêde çavkaniyên kitêb an ferhengên rastnivîsînê, ferheng û kovarên zimannasiyê hebin, ew awa wek awayê standard tên pejirandin.'' Yanî bilêvkirina xelkê ne xema me ye. Heta niha 4 kes gotine erê, ku hûn naxwazin em bi ya çavkaniyan bikin hingê dengê xwe bidin, bibêjin na daku ew rêzik nayên pêjirandin û em dîsa gotûbêj çêkin, rêzikên din hilbijêrin. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:11, 1 îlon 2020 (UTC)
::{{r|Balyozxane}} Min gotibû ''Diran (înternetê bigere "diranê" û "diranên") gelekî bêhtir ji "didan" ("didanê", "didanên") tê bikaranîn.''. Ew li ser çavkanîya ne. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:26, 1 îlon 2020 (UTC)
:::{{r|Guherto}} Hingê çima dengê xwe nadî? --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:27, 1 îlon 2020 (UTC)
::::{{r|Balyozxane}} Min hin sanîye berî ku tu vê peyama xwe ya talî şand dengê xwe da :) -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:28, 1 îlon 2020 (UTC)
:::::{{r|Guherto}} Mala te ava! :) --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:30, 1 îlon 2020 (UTC)
::::::{{r|Balyozxane}} Merheba. Min berê jî gotibû: ''rewşa kurmancên me gelek xirab e, u ji ber hindê pir kurmancên me bi xwendin u nivîsîna zimanê xwe nizanin''. Çavkaniyên niha bipiranî ji devokên navendî tên, hinên rojhilatê jî bi herfên erebî ne. Zimanê standard bi çavkaniyan çênabit, divêt li gorî zimanê hemî kurmancan çêbit. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 16:19, 2 îlon 2020 (UTC)
::::::{{r|Balyozxane}} Herwiha '''''[[didan]]''''' helbet dikarit wek '''''[[diran]]''''' bi lêv bibit, lê berevajî nabit. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 16:22, 2 îlon 2020 (UTC)
:::::::{{r|Hakîmo}} Merheba, fikirên xwe li dengdanê çima nadê? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 16:44, 2 îlon 2020 (UTC)
::::::::{{r|Guherto}} Ne min dayê... -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 12:53, 3 îlon 2020 (UTC)
Ez dibêjim ku "dinan" formêka rasttirin bi kurdî ye (bi kelhûrî bikar tînin). Em berhevdin:
Farsî: duzd, nazdīk, gandum, gand, dandān, xand-īd-an
Kurdî: diz, nezîk, genim, genî, dinan, ken-î-n
Zazayî: dızd, nezdi, genım*, gende, dından, (hûwaene)
Bi kurmancî, soranî u kelhûrî ZD u ND dibin Z u N, D dikeve. Ev qeyde gelêk kevin e.
*ji kurmancî, bi taliş qender: gāndım
[[Bikarhêner:Umut Akkoç|Umut Akkoç]] ([[Gotûbêja bikarhêner:Umut Akkoç|gotûbêj]]) 14:14, 18 gulan 2021 (UTC)
{{ds-kirî|diran}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:07, 1 hezîran 2025 (UTC)
== [[dur]] ==
[[dur]] û [[dir]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:41, 31 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[nanoziko]] ==
[[nanoziko]] / [[nanzikî]] / [[nano ziko]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 06:57, 4 îlon 2020 (UTC)
{{rfk|nanozikî|r=324}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:27, 27 nîsan 2025 (UTC)
{{ds-kirî|nanozikî}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:07, 1 hezîran 2025 (UTC)
== [[sînor]] / [[sinor]] ==
sînor / sinor
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:15, 4 îlon 2020 (UTC)
{{rfk|sînor|r=446}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:25, 27 nîsan 2025 (UTC)
{{ds-kirî|sînor}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:08, 1 hezîran 2025 (UTC)
== [[ard]] ==
ard / ar -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:10, 6 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:52, 27 nîsan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} ard rasttir dibînim -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:10, 6 îlon 2020 (UTC)
{{şîrove}} "ar" tê maneya "agir"ê jî. "Erd" (rûyê zemînê) û "ard" jî wek telafûz nêzî hev in. Di peyva "arvan"ê de jî wek "ar" derbas dibe. Bi min wek "'''ar'''" baştir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:52, 27 nîsan 2025 (UTC)
{{şîrove}} ard rasttir e [[Bikarhêner:Kurdistanov|Kurdistanov]] ([[Gotûbêja bikarhêner:Kurdistanov|gotûbêj]]) 07:08, 21 îlon 2026 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[ajne]] /[[avjenî]]/[[ajnî]] ==
[[ajnî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:05, 6 îlon 2020 (UTC)
* {{rfk|ajne, avjenî|r=42, 50}} Di vê rêbera rastnivîsînê de her du formên wek "ajne" û "avjenî" wek standard hatine qeydkirin. Lewma formên peyva ''ajne'' yên wek "ajnê", "ajnî", "aşnê" divê li madeya "ajne"yê werin alîkirin, lê "avjenî" bi serê xwe cih bigire. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:39, 21 îlon 2024 (UTC)
{{ds-kirî|[[avjenî]], [[ajne]]}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:09, 1 hezîran 2025 (UTC)
== [[gurî]] ==
[[gurî]] / [[gurîtî]] / [[gurriyatî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:53, 6 îlon 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[parsî]] ==
[[partî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:09, 7 îlon 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[xwestin]] ==
xwestin / xwastin
xwestin rasttir dibînim. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 18:09, 9 îlon 2020 (UTC)
{{rfk|xwastin|r=523}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:48, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ez jî '''''[[xwestin]]''''' rasttir dibînim; jixwe '''''[[xwastin]]''''' bi min giran têt, o '''''[[xwestin]]''''' di hemî lehceyên kirmancî da heye. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 08:21, 19 îlon 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[îrrasyonel]] ==
[[îrrasyonal]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:44, 9 îlon 2020 (UTC)
{{rfk||}} Ev peyv derbas nabe, feqet peyva "rasyonel" wek standard cih girtîye (r. 403). Lewma ev jî dibe "'''îrrasyonel'''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:46, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[ajel]]/[[ajal]] ==
ajel / ajal
Em dibêjin heywan :) -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:11, 10 îlon 2020 (UTC)
* {{rfk|ajel|r=42}} --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:34, 22 îlon 2023 (UTC)
=== Gotûbêj ===
<!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Peyva ajel ji peyva sewal a kurmancî hatiye standin (s>J) sewal anku ajel ew canewerên çarpayî yên kedîkirî ne. Wekî pez, bizin, çêlek.{{bêîmze|46.161.196.225}}
{{ds-kirî|ajel}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:11, 1 hezîran 2025 (UTC)
== [[pîrebok]]/[[pîrhebok]] ==
{{rfk|pîrebok|r=365}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:44, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ji bo Rastnivîsnê Ferhanga Kurdî; di pera 365 wek ''pîrebok'' hatiye standardîzekirin. Di ferhengê de herweha ev form jî hene: pîraboçk, pîrabok, pîraboşk, pîrabotîk, pîravok, pîravtong, pîreboşk, pîrebotk.
Wîkîferhengê herwiha forma pîrhebok heye[[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 21:15, 10 îlon 2020 (UTC)
{{ds-kirî|pîrebok}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:11, 1 hezîran 2025 (UTC)
== [[şirove]] / [[şîrove]] ==
[[şirove]] / [[şîrove]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:50, 11 îlon 2020 (UTC)
{{rfk|şirove|r=466}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:41, 6 adar 2025 (UTC)
{{ds-kirî|şirove}}--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 08:48, 5 tîrmeh 2026 (UTC)
== [[pezkûvî]]/[[pezkovî]] ==
[[pezkovî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:59, 11 îlon 2020 (UTC)
{{rfk|pezkûvî|r=354}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:41, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{r|Balyozxane}} Yek hatîye jêbirin. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:22, 23 çiriya pêşîn 2020 (UTC)
{{ds-kirî|pezkûvî}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:13, 1 hezîran 2025 (UTC)
== [[dibistana seretayî]]/[[dibistana seratayî]] ==
[[dibistana seratayî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:22, 12 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:39, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min eşkere ye ku awayê standard "sereta" ye, lewma ya standard dibe "''[[dibistana seretayî]]''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:39, 6 adar 2025 (UTC)
{{ds-kirî|dibistana seretayî}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:13, 1 hezîran 2025 (UTC)
== [[sêzdeh]]/ [[sêzde]] ==
/ sêzde.
sêzdeh rasttir dibînim, sê + z + deh. Ti sedem nîne ji bo jêbirina "h" -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:34, 13 îlon 2020 (UTC)
{{rfk|sêzdeh|r=441}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:37, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ferhenga Michael L. Chyet (2003) sêzde ber alî dike sêzdeh. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:34, 13 îlon 2020 (UTC)
: yazdeh/yazde (beralîkirin), çardeh/çarde (dubare), pazdeh/pazde (beralîkirin), şazdeh/şazde (beralîkirin), hevdeh/hevde (beralîkirin), hejdeh/hejde (beralîkirin), nozdeh/nozde (beralîkirin) -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:37, 13 îlon 2020 (UTC)
{{ds-kirî|sêzdeh}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:14, 1 hezîran 2025 (UTC)
== [[çirûsk]]/[[çirîsk]] ==
çirîsk/çirûsk
çirûsk(m):1.kıvılcım, çıngı
çirîsk(m): binêre(çirûsk)(per 116,117)[[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 22:46, 13 îlon 2020 (UTC)
{{rfk|çirûsk|r=116}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:36, 6 adar 2025 (UTC)
{{ds-kirî|çirûsk}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:15, 1 hezîran 2025 (UTC)
== [[êriş]]/[[êrîş]] ==
[[êriş]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:11, 15 îlon 2020 (UTC)
* {{rfk|êriş|r=157}} --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:36, 22 îlon 2023 (UTC)
{{ds-kirî|êriş}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:15, 1 hezîran 2025 (UTC)
== [[mûmin]] ==
[[momîn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:22, 16 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:00, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ya standard "'''mumin'''" e. Bi apostrof baştir be jî, ji ber nestandardbûna emilandina apostrofan, ji bo halê standard bêapostrof baştir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:00, 5 sibat 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[xwarza]] ==
[[xwarza]] / [[xwarzî]] / [[xuharzî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:14, 17 îlon 2020 (UTC)
{{rfk|xwarza, xwarzî|r=523}} Her du peyv jî bi eynî maneyê di ferhengê de cih girtine. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:34, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[lênêrîn]] ==
[[nerîn]] / [[nêrîn]] / [[lênêrîn]] / [[lê nerîn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:19, 17 îlon 2020 (UTC)
{{rfk|nêrîn|r=333}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:04, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} "Lênêrîn" jixwe wek navdêr "''nêrîna li tiştekî''" ye, û "lê nêrîn" jî wek lêker "''li tiştekî nêrîn''" e, lê belê di her dukan de jî lêker/navdêr "nêrîn" e. "li"ya pêşî ji bo maneya "nêrîn"ê bêtesîr e/tesîreke wê lê nîne. Feqet ger were gotin ku "nêrîn" (bi îngilîzî "looking") wek forma "lênêrîn" xwedî maneyeke din e (ku divê ev yek jî bi çavkanîyan were îsbatkirin) wê gavê mirov kare wê peyvê cardin binirxîne. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:04, 5 sibat 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[hewar]] ==
[[hewar]]/[[hawar]] --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 10:07, 19 îlon 2020 (UTC)
{{rfk|hawar|r=190}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:35, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[muhakeme]] ==
[[muhakeme]] / [[mihakeme]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:59, 19 îlon 2020 (UTC)
{{rfk|mihakeme|r=309}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:34, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[tevabûn]] ==
[[tewabûn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:22, 19 îlon 2020 (UTC)
{{rfk|temambûn|r=477}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:32, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Tev jî formên "''temambûn''"ê ne. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:32, 5 sibat 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[fiêl]] ==
[[fiêl]] / [[fîil]] / [[fîîl]] Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:12, 19 îlon 2020 (UTC)
{{rfk|fêl|r=164}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:21, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[pêşniyazkirî]] ==
[[pêşniyarkirî]], [[pêşniyar]] beralî dike [[pêşniyaz]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:24, 19 îlon 2020 (UTC)
'''Eynî mijar li jêr hatîye vekirin. Ji bo standardkirinê binêrin ya jêr.''' -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:18, 5 sibat 2025 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[xwehr]] ==
[[xwehr]] / [[xwar]] Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:43, 20 îlon 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Bikarhêner}} Kîjan mane? Jixwe ya xwehr (îng crooked) standardkirî bû lê te xwehr kopî kir xwar. Çima? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:07, 20 îlon 2020 (UTC)
: {{r|Guherto}} Merheba bira. Li ser rûpela "[[xwar]]" maneya rengdêrî ya "ne rast, tewandî" tinebû. Tenê maneyeke wek "çewt, xelet" ''(bi cureyê rengdêrî)'' hebû. Ji ber ku mane kêm û şaş hatibû dayîn min ji rûpela "[[xwehr]]" mane û beşên din kopî kir û bi rûpela "[[xwar]]" ve kir. Û ji ber ku ji bo tenê maneyekê du awayên nivîsê yê wek "[[xwar]]" û "[[xwehr]]" wek standard cih digirtin, min şablona standardkirinê tevlî wan her du rûpelan jî kir, û ji bo ku em awayê standard bibijêrin li vir beşekê nû vekir. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:25, 20 îlon 2020 (UTC)
::Tu dikarîbû bnr-ek lê zêde bikê, ne hewce ye kopîkirin. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:48, 20 îlon 2020 (UTC)
::: {{r|Guherto}} Ji ber ku ez awayê standard wek "[[xwar]]" dibînim min rûpelê wek standard hazir kir. Awayê bnr-yî ne awayê wek standard e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:07, 20 îlon 2020 (UTC)
::::Temam. Lê hêjî kopîkirin ne hewce ye, bnr min çawa kir li [[#girover]] ji ber ku ez wê standard didîtim (dibînim). -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 10:11, 20 îlon 2020 (UTC)
::::: {{r|Guherto}} Baş e, di yên din de êdî ez ê wisa bikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:35, 20 îlon 2020 (UTC)
xwehr = ne rast (tirkî:eğik) û xwar = jêr (tirkî:aşağı).—<small>[[Bikarhêner:Gomada|<span style="background:Yellow">'''''<span style="font-family:Verdana;color:red">Gom<span style="color:#006400">ada</span></span></span>''''']]<sup>[[Gotûbêja bikarhêner:Gomada|peyam]]</sup></small> 15:34, 21 kanûna paşîn 2021 (UTC)
{{şirove}} Birêzan; li herêma me ev peyv wek ''xar'' tê bilêv kirin û li serhedê heta Sêwaz û ji wir hata Rojavayê Kurdistan bi vî awayîye.Min xwehr heya niha ji devê kesî nebihîstibû. Bi ya min xwar rasttire. Ji ber ku li devê meda di nav gellek peyvanda tîpa ''w'' nîne. Lê îjar jî bi peyva ''xwarin'' û ''xwarê''(berjêr) tevlihevî çêdibe.Dema ku standardizasyon çêdibe divê hemî peyvên watecûda bi wekhev neyê nivîsandin. Wek mînak niha peyvên kêr(alav,feyde,pozdewixî), ker(ajal,parçe,koma pez,nebihîz) 3-4 peyven ku xwendinên wan cûdane bi heman awayî tê nivîsin,ev zimanê me zortir dike û dibe sedema xeletîyan.Divê ziman standardîze be, lê ne bi vî awayî. {{bêîmze|Mihemed Qers}}
== [[dirist]] ==
[[dirist]] / [[dirust]]
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 13:44, 20 îlon 2020 (UTC)
{{rfk|dirust|r=137}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:15, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[neynok]] ==
[[neynok]] / [[neynûk]] / [[nînok]] / [[nênûk]] / [[nenîk]]
Awayê ku herî kêm tê bikaranîn (nînok) weke standard hatîye neqandin, ez vê şaş dibînim. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:43, 20 îlon 2020 (UTC)
Eynî rûpel li nînok û nênûk hene. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:45, 20 îlon 2020 (UTC)
{{rfk|nenûk|r=328}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:10, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi qenaeta min divê halê standard "neynûk" be. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:10, 5 sibat 2025 (UTC)
== [[neynokbir]] ==
neynokbir/neynûkbir/nenîkbir
Divê li gora encama [[#neynok]] werê standardkirin. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:39, 20 îlon 2020 (UTC)
{{rfk|nenûk|r=328}} Di vê ferhengê de ''nenûkbir'' nîne, lê ji bo peyva "neynûk"ê awayê standard wek "nenûk" hatîye nîşandayîn. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:12, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min wek halê standard forma "neynûk" baştir e. Lewma ev jî dibe "''neynûkbir''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:12, 5 sibat 2025 (UTC)
== [[xwijok]] ==
[[xişok]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 02:46, 21 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:44, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ji ber ku halê wê yê lêker wek "''xişîn''" e, ya standard "'''xişok'''" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:44, 5 sibat 2025 (UTC)
== [[pirhindik]] ==
[[pir hindik]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:34, 22 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:30, 3 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Formeke wek "pirhindik" bi min bêçavkanî ye. Lewma ya standard divê wek "pir hindik" be. Feqet peyva "pir hindik" jî bi maneya "teqrîben, nêzîkî" nîne. Divê ew qism jî were rastkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:30, 3 sibat 2025 (UTC)
:Min maneya wê fam kir, feqet nizanim, emilandineke wisa heye an tine. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:26, 6 adar 2025 (UTC)
== [[qireqir]] ==
[[qîreqîr]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:22, 23 îlon 2020 (UTC)
{{rfk|qîreqîr|r=390}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:11, 3 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[pênivîs]] ==
[[pênivîs]] / [[pênûs]] / [[pênivîsk]].
Pênivîs bêhtir tê bikaranîn. Ev û pênûs eynî peyv in, lê pênûs li gora "nûsîn"a soranî û pênivîs li gora "nivîsîn"a kurmancî hatîye çêkirin. Em wan bikin yek?
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:58, 24 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:34, 3 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
# pênivîs - Ferhenga Birûskî Michael L. Chyet
# pênivîs - Ferhenga Destî çap 2 Umîd Demîrhan
# pênivîs - Ferhenga Kurdî ( Kurmancî ) - Farsî. Koma Kurdîya Kurmancî. Azad MAKÛYÎ,Haşim MEHMÛDÎ,Şehnaz OMERZADE, Nasir ECEMÎ,Mohsin QULÎZADE,Kejal PADAŞ,Efsûn YÛSIFÎ
# pênivîsk - Rehnas Husein Muhammed
{{şîrove}} Qertafa "-k"yê di çêkirina navdêran de li kar e. Lewma ji alîyê xisûsîyeta navderbûnê ve "''pênivîsk''", ji alîyê pratîkbûnê ve ''"pênivîs"'' baştir e. Lê eger krîtera girêdayîmayîna bi eslê peyvê ve were esasgirtin, wê gavê jî ''"pênûs"'' baştir e. Biqenaeta min "'''pênivîsk'''".
: Ji xeynî vê qismeke ji [https://zimannas.wordpress.com/2015/12/21/em-cima-kurdi-li-ber-xwe-dikin-ciyayeki-ase/ nivîsa malpera Zimannasê]:
: ''[Peyva ”qelem” ji aliyê hemû kurdan ve û ji hêla miletên misilman ve ji Indonezyayê heta Mexribê tê fehmkirin û bikaranîn. Lê nivîskarên me qîma xwe bi wê naînin ji ber ku bi xeletî bawer dikin ku eslê wê erebî ye. Lê eslê wê jî yûnaniya kevn e: κάλαμος (kálamos). Li cihê wê nivîskarên me xwe dispêrin peyvên çêkirok yên wek ”pênivîs(k)” yan jî ”pênûs” ya ji soranî wergirtî. Lê ”pênivîs(k)” yan ”pênûs” her tişt e ku mirov pê dinivîse, ne tenê qelem e: li vê serdemê mirov zêdetir ne bi qeleman lê bi kompûterê, bi telefona mobîl yan bi hin makîneyên din dinivîse. Amûrên nivîsînê yên serekî niha ne qelem in lê kompûter, laptop, tablet û telefon in. Li gor vê rêbazê be, divê mirov ji kompûteran, ji laptopan re, ji tabletan re û ji telefonan re jî bibêje ”pênivîsk/pênûs”, ne tenê ji qeleman re!]'' -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:34, 3 sibat 2025 (UTC)
== [[behs]] ==
/ [[bas]].
Em dibêjin beḧs. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:49, 24 îlon 2020 (UTC)
{{rfk|behs|r=64}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:42, 1 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[dîmender]] ==
[[dîmender]] / [[dîmendêr]].
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:43, 25 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:45, 27 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Paşgira "der" / "dêr"ê li şûna hev hatine emilandin. Lê ji nimûneyên jêrîn ên ji ferhenga ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' tê dîtin ku qerareke li ser zelalkirina halê standard nehatîye dayîn di wê ferhengê de.
Bnr. [[agahîder]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 41), [[hander]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 189), [[kêfder]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 253),
[[avdêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû wek "avdêr" nîne, lê wek "''avdêrî''" r. 50), [[bangdêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 58), [[dengdêr]] ''-ne zelal e, nimûneyên din fahmbartir in-'' (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 127), [[kardêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 239), [[saloxdêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 425), [[serîhildêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 435),
[[xwepêşander]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne), [[dersder]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne), [[fermandêr]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de ev form nîne, wek "''fermande''" qeyd kirine), [[deynder]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne), [[girêdêr]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne), [[mizgînder]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne).
Bi min divê em li balê bidin ser ku paşgirên "der" / "dêr"ê eynî maneyê didin an na. Bi min paşgira "der"ê qismen nêzî paşgira "-dar"ê ye ku ew jî maneya xwedîbûna tiştekî dide. Lewma bi min ji bo peyvên bi maneya "kesê tiştekî dide" emilandina paşgira "-dêr"ê wê baştir be, lê ger em ji alîyê rehetîya telafûzê bifikirin jî paşgira "-der"ê baştir e ji bo awayê standard. Wek netîce bi min ji bo halê standard (ji bo peyvên bi maneya "kesê tiştekî dide") paşgira "-dêr"ê başttir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:45, 27 kanûna paşîn 2025 (UTC)
== [[Rojavayê Kurdistanê]] ==
/Rojavaya Kurdistanê.
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 12:16, 26 îlon 2020 (UTC)
[[Rojavayê Kurdistanê]]-- [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 21:12, 9 adar 2021 (UTC)
{{rfk|"rojava" wek nêr derbas dibe|r=416}} Peyva "rojava" wek nêr qeyd kirine. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:13, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} ''{{g|ku|rojava}}'' navdêreke mê ye, li gori rêzimana kurdi nivîsa rast "Rojava'''ya''' ''(îzafe)'' Kurdistan'''ê''' ''(çemandî)''" ye. Feqet "Rojavayê Kurdistanê" jî pirr tê bikaranîn.. --[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:27, 9 adar 2021 (UTC)
{{şîrove}} Slav @[[Bikarhêner:Ghybu|Ghybu]], gelle me û mezinên me dema ku peyvan bilêv dikin nêr/mê bûna peyvan çîye nizanin,peyv û hevokên ku hezaran salanda terkîb bûne û derketine holê bi xwezayî ji devên wan dertê, heta eger ku ji wanra nêr/mê bûna peyvan qal bikin yê li me xerîb binêrin,lê wan bi hezaran saland a bê qest û bi xwezayî vî zimanî qasê ku meriva şaş bihêle bi duzen ava kirine. Ne tenê herêma me, min ji gellê çi bihîstîye wê dinivîsim. Lê ji bilî gellê meyê hin herêmên Mêrdînê min tu cîyek nebihîstye ku mînak şûna ″porê min″ ″pora min″,yan jî şûna ″Rojavayê Kurdistanê″ ″Rojavaya Kurdistanê″ bêjin. Lê bi ya min em nikarin bêjin jiber ku peyva ″roj″ mê ye divê ″rojava″ jî mê be. Rast e rojava ji roj tê lê edî rojava peyveke cûda ye. [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 23:09, 9 adar 2021 (UTC)
{{şîrove}} Di Rêbera Rastnivîsînê ya Komxebata Kurmancîyê de (r.40) jî wek alî (wek hêl, ne parçe) "'''rojavayê''' Kurdistanê" derbas kirine. Ji ber ku eynî peyv e, em dibînin ku li wir jî wek "nêr" qebûl kirine. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:13, 26 kanûna paşîn 2025 (UTC)
== [[diz]] ==
[[diz]] / [[dizîker]] / [[dizîkar]]. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 12:37, 27 îlon 2020 (UTC)
{{rfk|diz, dizek|r=138}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:06, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} ″diz″ ji xwe kesek e ″dizî″ jî karesata wî/ê ye lewma ″dizîker/dizîkar″ xeletin.[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 20:38, 9 adar 2021 (UTC)
:Kes nabêje "dizîker,/dizîkar", kurd dibêjin : dizek . [[Taybet:Beşdarî/83.252.83.100|83.252.83.100]] 19:25, 3 gulan 2023 (UTC)
{{şîrove}} Şiroveyên hatî yên berê ji bo min jî rast dixuyin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:06, 26 kanûna paşîn 2025 (UTC)
== [[rêkxistin]] ==
[[rêxistin]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:03, 27 îlon 2020 (UTC)
*{{rfk|rêxistin}}--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:27, 23 îlon 2023 (UTC)
=== Gotûbêj ===
<!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} [[rêxistin]] -- [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 20:39, 9 adar 2021 (UTC)
== [[cihêl]] ==
[[cihêl]] / [[cehêl]] standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:38, 29 îlon 2020 (UTC)
{{rfk|cahil|r=94}} Forma standard wek "'''cahil'''" hatîye nîşandan, ji bo kelîmeya "cihêl". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:10, 4 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[tihn]] ==
[[tên]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:41, 30 îlon 2020 (UTC)
{{rfk|tîn|r=488}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:01, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[gişt]] ==
[[gişt]] / [[giş]] / [[gî]] / [[gîh]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:25, 30 îlon 2020 (UTC)
{{rfk|gişt|r=179}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:55, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[tal]] ==
[[tehl]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:35, 1 çiriya pêşîn 2020 (UTC)
{{rfk|tahl|r=472}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:51, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Birêzan pirranîya gellê me vê peyvê wek ″تعال″ bilêv dike lê alfabeya me da tîpa ″ع″ tune.Divê em jibo vê tîpê standartekî çêkin, wek şûna ″ع″ ″a yan jî eh″ bikarbînin wê demê pirsgirêk yê jibo peyvên din jî çareser be. -- [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 21:31, 9 adar 2021 (UTC)
== [[şuxl]] ==
[[şol]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 02:56, 2 çiriya pêşîn 2020 (UTC)
[[şuxil]]--[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 21:41, 9 adar 2021 (UTC)
{{rfk|şixul|r=466}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:50, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[gilîz]] ==
[[girêz]] / [[gilîz]] / [[gilêz]] -- [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:25, 5 çiriya pêşîn 2020 (UTC)
:girêz [[Taybet:Beşdarî/~2026-20662-33|~2026-20662-33]] ([[Gotûbêja bikarhêner:~2026-20662-33|talk]]) 19:33, 3 nîsan 2026 (UTC)
{{rfk|gilêz|r=176}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:46, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Çi wê ê standardkirin? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:27, 14 çiriya paşîn 2020 (UTC)
{{şîrove}} [[girêz]] -- [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 20:42, 9 adar 2021 (UTC)
== [[waris]] ==
/ [[wêris]]
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:03, 6 çiriya pêşîn 2020 (UTC)
{{rfk|waris|r=504}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:00, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[rêveber]] ==
/birêveber. Ber alî dike "birêveber" lê bahwer im "rêveber" bêhtir tê bikaranîn. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:50, 12 çiriya pêşîn 2020 (UTC)
{{rfk|birêveber|r=86}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:15, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min her çiqas lêker "''bi rê ve birin''" be jî halê standard wek "''rêveber''" be baştir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:15, 26 kanûna paşîn 2025 (UTC)
== [[jêk cuda bûn]] ==
/jê cuda bûn. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:15, 15 çiriya pêşîn 2020 (UTC)
{{rfk|ji hev cihê bûn, ji hev cuda bûn|r=230}} Forma "jêk" (ji yek) di vê ferhengê de derbas nabe. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:13, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} [[jê cuda bûn]]--[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 18:18, 7 adar 2021 (UTC)
{{şîrove}} Di "Cuda bûn" / "cihê bûn"ê de jixwe du tişt ji hev cuda dibin, lewma bi min madeyeke wek "''jê cuda bûn''" / "''jê cihê bûn''" (ji tiştekî cuda bûn / ji tiştekî cihê bûn) nehewce (neferhengî) ye. Forma "''jêk''" ''(ji yek)'' di ferhenga 'Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû' de derbas nabe.
Lewma bi dîtina min her du formên mewcûd jî nestandard in, lê belê ya standard "'''''ji hev cuda bûn'''''" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:13, 26 kanûna paşîn 2025 (UTC)
== [[pakêt]] ==
/paket. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:04, 24 çiriya pêşîn 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:56, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû de (r. 346) ji bo halê standard yê kelîmeya "''parsêl''"ê "''parsel''" nîşan dane. Ev jî wekî wî ye, '''lewma halê standard "''paket''" e''', lê ne "''pakêt''". Ji ber ku telafûza "''pakêt''"ê jî li gor yê din zehmettir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:56, 26 kanûna paşîn 2025 (UTC)
== [[hevaltî]] ==
/ [[hevalî]]. Hevaltî rasttir e û bêhtir tê bikaranîn. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:32, 31 çiriya pêşîn 2020 (UTC)
{{rfk|hevalî, hevaltî|r=200}} Di vê ferhengê de her du formên wek "''hevaltî''" û "''hevalî''" jî wek standard hatine qebûlkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:52, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min divê em tenê yekî ji wan wek ya standard bibijêrin. Ew jî divê '''''hevaltî''''' be. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:52, 26 kanûna paşîn 2025 (UTC)
== [[êzidî]] ==
[[êzîdî]]/[[êzdî]] - -[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:04, 11 çiriya paşîn 2020 (UTC)
{{rfk|êzidî|r=158}} û wek kelîmeya jêçêbûyî: "êzidîtî". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:48, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} [[êzdî]]--[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 18:16, 7 adar 2021 (UTC)
== [[potîn]] ==
[[putîn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:04, 14 çiriya paşîn 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:59, 19 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di pir cihan de wek "potîn" derbas bûye, lê min li ti derê forma "putîn" nedît. Lewma bi min eşkere ye ku halê standard wek "'''potîn'''" e. Û ger forma din bi temamî bêçavkanî be, em wê formê jî jê bibin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:59, 19 adar 2025 (UTC)
== [[diwazdeh]] ==
[[diwazdeh]] / [[dazde]]
{{rfk|duwazdeh|r=142}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:19, 4 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[xwende]] ==
[[xwenda]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:47, 25 çiriya paşîn 2020 (UTC)
{{rfk|xwende|r=524}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:17, 4 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ji bo kelîmeya "'''xwende'''" divê forma standard "xwende" be, ne ku "xwendî". Maneya kesê perwerdegirtî û kesê du-kelîme-xwendî divê ne eynî be. Lewma ez kelîmeya "xwende" pêşnîyar dikim ji bo awayê standard. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:17, 4 çiriya paşîn 2024 (UTC)
== [[pêşniyaz]] ==
[[pêşniyaz]] / [[pêşniyar]] / [[pêşniyaz kirin]] / [[pêşniyar kirin]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:06, 25 çiriya paşîn 2020 (UTC)
{{rfk|pêşnîyaz|r=359}} Kelîmeyên jêçêbûyî ên "pêşnîyaz kirin", "pêşnîyazî", "pêşnîyazkirin" jî wek standard hatine nîşandan. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:12, 4 çiriya paşîn 2024 (UTC)
:Dibe ku ev peyv pêşniyaz be ne pêşn(î)yaz. Ev peyv hîn bêtir nêzî hizir e. Lewra niyaz wateya daxwazê dide lê niyar wateya neyartiyê dide. Divê ev peyv pêşniyaz be. [[Bikarhêner:Mûhemmed Seîd|Mûhemmed Seîd]] ([[Gotûbêja bikarhêner:Mûhemmed Seîd|gotûbêj]]) 21:03, 28 tebax 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Balyozxane}} Wê çi ê standardkirin? Yan ti dixwazê ya standard werê guhertin? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:06, 25 çiriya paşîn 2020 (UTC)
:{{r|Guherto}} Nabînî pêşniyar dibêje pêşniyaz rast e pêşniyaz kirin dibêje pêşniyar kirin rast e. Kîjan rast e? --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:21, 25 çiriya paşîn 2020 (UTC)
::Qusûrê ne mêzêne, mi tenê rûpela pêşniyaz vekir û dît ku dibêjê pêşniyaz / pêşniyar. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:56, 25 çiriya paşîn 2020 (UTC)
== [[kamil]] ==
[[kamil]], [[kamîl]], [[kamul]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:02, 29 çiriya paşîn 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:49, 3 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ev peyveke Erebîye, lê dema ku Kurd wê bilêv dikin ″kamil″ dibêjin.Lewma ″kamil″ rasttire, ″kamîl û kamul″ bilêvkirinên tirk û hindîyane. [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 17:26, 7 adar 2021 (UTC)
{{şîrove}} Ya standard bi eşkereyî "kamil" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:49, 3 çiriya paşîn 2024 (UTC)
: Lê di Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî) - Tirkî ya Nû ya M. Malmîsanij û yên din de cih dane madeya "tekamil" û formên wê yên nestandard ên wek ''tekamîl'', ''tekamul''. Lewma ya standard li gor vê ferhengê dide "'''kamil'''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:54, 3 çiriya paşîn 2024 (UTC)
= Daxwazên standardkirinê: 2021 =
== [[fêrgeh]] ==
[[fêrgeh]] / [[fêrge]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:58, 5 kanûna paşîn 2021 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:36, 3 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} [[fêrgeh]] bê guman rasttire, ev tîpa ″h″ di gellek peyvanda li hin herêman nayê bilêvkirin.Lê ev nayê manê ku ″h″ tunne.
{{şîrove}} Herçend ev kelîme di Ferhenga Kurdî ya Rastnivîsînê ya M. Malmîsanij û yên din de derbas nabe jî, ji madeyên "azmûngeh" (r. 54), "dadgeh" (r. 120), "dangeh" (r. 122) û yên din tê famkirin ku ji bo paşgira "-geh"ê (formên din: "-ge") ya standard "-geh" e. Lewma em karin bêjin ku ji bo vê kelîmeyê divê ya standard bibe "fêrgeh". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:36, 3 çiriya paşîn 2024 (UTC)
== [[bêhêwî]] ==
[[bêhewî]]
Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:52, 23 kanûna paşîn 2021 (UTC)
{{rfk|hewî|r=202}} Ji bo maneya "kuma" awayê standard wekî "hewî" hatiye nîşandan. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:25, 3 îlon 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Peyvên "hewî" û "hêwî" her du jî xweyî çendîn maneyan in. Lewma ger beralîkirin were danîn divê maneya ku jê tê beralîkirin jî were nivîsîn. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:25, 3 îlon 2023 (UTC)
== [[pêşnûma]] ==
[[pêşnima]]
Divê bên standardkirin. Bi îxtîmalekî mezin ya standard "pêşnima" ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:20, 12 sibat 2021 (UTC)
'''Eynî mijar li jêr hatîye vekirin. Ji bo standardkirinê binêrin ya jêr.''' -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:15, 3 çiriya paşîn 2024 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
[[pêşnima]]--[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 18:13, 7 adar 2021 (UTC)
== [[jenosîd]] ==
[[jînoside]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:39, 13 sibat 2021 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:13, 3 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} "jînoside" an "jînosid" ? Min nivîsandinên formên "jînosideyê" an "jînosideya" pirr nedîtiye...--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:48, 13 sibat 2021 (UTC)
{{şîrove}} Bi min ya standard "[[jenosîd]]" e. Halên din ên "cenosîd", "jînosîd", "jînosid" kêm-li-kar in. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:13, 3 çiriya paşîn 2024 (UTC)
== [[kesaxtin]] ==
- [[kezaxtin]]
Standardkirin hewce ye. Bi ya min ya rast "kesaxtin" e, yê din xelet e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:57, 14 sibat 2021 (UTC)
{{rfk|kesaxtin|r=250}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:07, 3 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[derazîng]] ==
/ [[derazînk]]
Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:54, 19 sibat 2021 (UTC)
{{rfk|derazînk|r=127}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:53, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Bikarhêner}} bira navê sernavî neguherîne. Parametreya yekem ya şablona {{ş|ds}} divê sernavê vê gotûbêjê be.--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:51, 19 sibat 2021 (UTC)
== [[rovî]] ==
[[rûvî]]/[[rîvî]]. Bi ya min ji bo maneya "guhandarek" rovî zêdetir tê bikaranîn. Bi maneya "hinav" jî du rûpel ([[rûvî]]/[[rîvî]]) hene.--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:19, 19 adar 2021 (UTC)
{{rfk|rûvî|r=423}} Bi maneya "ince bağırsak". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:40, 30 çiriya pêşîn 2024 (UTC)
{{rfk|rovî|r=419}} Bi maneya "tilkî". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:41, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[esman]] ==
[[esman]] | [[asman]] | [[ezman]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:00, 24 adar 2021 (UTC)
{{rfk|esman|r=152}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:38, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ji ber ku ji didu peyvan pêk tê wek [[asê]]+[[man]]. Ezê peyva [[asman]] pêşniyar bikim. [[Bikarhêner:Key Mîrza|Key Mîrza]] ([[Gotûbêja bikarhêner:Key Mîrza|gotûbêj]]) 16:30, 10 gulan 2021 (UTC)
== [[niha]] ==
[[niha]] / [[niho]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:22, 25 adar 2021 (UTC)
{{rfk|niha|r=333}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:35, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[sûper]] ==
Standardkirinê teleb dikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:55, 26 adar 2021 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:27, 3 îlon 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Li gorî telefûzê ya standard "super" e, kes halê dirêjkirî nabêje, bi qasî zanîna min. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:48, 30 çiriya pêşîn 2024 (UTC)
== [[sûperlatîv]] ==
Standardkirinê teleb dikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:57, 26 adar 2021 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:28, 3 îlon 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi ya min ya rast "super" û "superlatîv" e, ji ber ku gava em bi lêv dikin em wek "superlatîv" bi lêv dikin, ne wek "sûperlatîv". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:57, 26 adar 2021 (UTC)
== [[radîkalîzm]] ==
/[[radikalîzm]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:21, 30 nîsan 2021 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:46, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Balyozxane}} Bi min peyveke wekî "radikalîzm" tine ye û bi xeletî wisa hatiye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:16, 17 tebax 2022 (UTC)
== [[texdîr]] ==
[[teqdîr]] standardkirin hewce ye. Bi min "teqdîr" standardtir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:52, 18 gulan 2021 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:45, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Pir eşkere ye ku ya standard "'''teqdîr'''" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:45, 30 çiriya pêşîn 2024 (UTC)
== [[tayîn]] ==
[[teyîn]] / [[tayîn]] / [[teîn]] / [[tayin]]. Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:30, 9 tîrmeh 2021 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:55, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Mirov di navbera "tayîn" û "tayin" de bêqerar dimîne. Lê bi min divê wek halê standard "'''tayîn'''" were dîyarkirin. Ji ber ku zêdetir li kar e (di lêgera Google Searche de jî zêdetir "tayîn" derdikeve, ''formata lêgerînê: wek "tayîna xwe" / "tayina xwe"''). -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:55, 29 çiriya pêşîn 2024 (UTC)
== [[nod]] ==
* [[not]]
Ev made li vir heye: [[Wîkîferheng:Daxwazên_standardkirinê#not,_nod]] Bi şaşî tekraren hatîye tevlîkirin. Ji bo agahîyan lê binêrin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:00, 29 çiriya pêşîn 2024 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Zanavano}}. Bnr. [[WF:Rêzikên standardkirinê]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:28, 1 çiriya pêşîn 2021 (UTC)
= Daxwazên standardkirinê: 2022 =
== [[xizar]] ==
[[xizar]], [[xîzar]]. Standardkirin hewce ye.{{bêîmze|Bikarhêner}}
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:46, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Forma xizar hema bêje li ti derê tine ye. Lewma halê standard divê "xîzar" be. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:46, 29 çiriya pêşîn 2024 (UTC)
== [[Nû Zelenda]] ==
[[Zelandaya Nû]] zêdetir tê bikaranîn.[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:18, 9 kanûna pêşîn 2022 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:39, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di '''Rêbera Rastnivîsînê''' ya Komxebata Kurmancîyê de (r. 37) wek "Zelandaya Nû" hatîye qeydkirin. Di kitêba '''Rastnuştişê''' ya Gruba Xebatê ya Vateyî de jî ev peyv wek "Zelandaya Newîye" (r. 67) hatîye qeydkirin. Lewma bi min divê halê standard bibe "Zelandaya Nû." -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:39, 29 çiriya pêşîn 2024 (UTC)
= Daxwazên standardkirinê: 2023 =
== [[pirzik]] ==
[[pizik]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:37, 15 kanûna paşîn 2023 (UTC)
* {{rfk|pizrik}} --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:37, 15 kanûna paşîn 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min forma wek "{{g|ku|pizrik}}" jî heye û divê ew jî were standardkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:53, 31 tebax 2023 (UTC)
== [[bûro]] ==
{{g|ku|bûro}} / {{g|ku|buro}} / {{g|ku|buriyo}} standardkirin hewce ye. --- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:06, 22 kanûna paşîn 2023 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:58, 31 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di ''Rêbera Rastnivîsînê'' ya Komxebata Kurmancî de di rûpela 139em de nimûneyeke wek "'''buroya wî'''" hatiye dayîn. Ji vê nimûneyê mirov dikare bêje ku ji bo vê peyvê awayê standard wekî "buro" dibînin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:53, 3 îlon 2023 (UTC)
== [[fezîlet]] ==
[[fazîlet]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:47, 26 kanûna paşîn 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ev peyv jî ji erebî tê. Herçiqas di hin peyvên reh-erebî de (wek nimûne "edalet") ji bo eyna erebî "e" hatibe tercîhkirin jî bi min ji bo em li vir nêzî telafûza di pratîkê de bibin, forma bi "e"yê neyê tercîhkirin jî dibe. Lewma ez "fazîlet"ê pêşnîyar dikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:57, 29 çiriya pêşîn 2024 (UTC)
== [[Uman]] ==
{{g|ku|Uman}}, {{g|ku|Oman}}, {{g|ku|Iman}}, {{g|ku|'Iman}}, {{g|ku|'Uman}}, {{g|ku|Umman}}
Standardkirin hewce ye.
{{rfk|}}
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di '''''Rêbera Rastnivîsînê''''' ya Komxebata Kurmancîyê de awayê "'''Uman'''" wekî standard hatiye nîşandan. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:34, 3 nîsan 2023 (UTC)
== [[hejî]] ==
{{g|ku|hejî}}, {{g|ku|hêja}}
Standardkirin hewce ye.
{{rfk|}}
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Ferhengvan}} Bi min ev herdu, wekî formên eynî peyvê dixuyin. Lewma standardkirinê teleb dikim. Her çiqas berçav be ku awayê stadard "hêja" ye jî, li vir teleb dikim. Lê ger ne formên eynî peyvê bin, xêra xwe li vir binivîsin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:52, 7 nîsan 2023 (UTC)
::{{Silav|Bikarhêner}} Rast dibêjî, "hejî" formeke devokî (behdînî) ya peyva "hêja" ye. --[[Bikarhêner:Ferhengvan|Ferhengvan]] ([[Gotûbêja bikarhêner:Ferhengvan|gotûbêj]]) 20:31, 7 nîsan 2023 (UTC)
== [[sererast]] ==
{{q|ku|sererast}}, {{q|ku|serrast}} Standardkirin hewce ye. Bi min xuya ye ku forma standard "sererast" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:02, 25 nîsan 2023 (UTC)
{{rfk|serrast, serrast kirin|r=437}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:41, 31 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bila piştî gihîştina netîceyê, standardkirina formên din ên wekî "sererastkirin" / "serrastkirin", "sererast kirin" / "serrast kirin" jî neyê jibîrkirin. Bi min divê forma standard bibe "sererast". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:41, 31 tebax 2023 (UTC)
== [[fiqih]] ==
{{g|ku|fiqh}} / {{g|ku|fiqih}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:16, 30 nîsan 2023 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:16, 30 nîsan 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di ''Rêbera Rastnivîsînê'' ya Komxebata Kurmanciyê de (r.52, 53) behsa vê mijarê kirine: Bi sernavê "'''Nivîsîna hin bêjeyên bîyanî yên yekkîteyî ku di kurmancîyê da dibin dukîteyî'''", wek nimûne jî "'''aqil ✓''', '''aql ✗'''", "fikir ✓, fikr ✗", "qebir ✓, qebr ✗", "qebiz ✓, qebz ✗", "qedir ✓, qedr ✗", "qehir ✓, qehr ✗"...
Li binî jÎ dibêjin divê li gel îzafeyê [i] bê parastin, wekî "aqilê min ✓, aqlê min ✗", "fikira min ✓, fikra min ✗", "qebira min ✓, qebra min ✗"...
Lewma bi dîtina min, li gor vê rêberê divê wekî "fiqih" bibe ya standard. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:38, 31 tebax 2023 (UTC)
== [[îqaz]] ==
{{g|ku|îqaz}} - {{g|ku|îkaz}}. Standardkirin hewce ye. Piştî qerardana li ser forma standard, divê peyvên girêdayî wan jî yên wek "îqazker", "îqaz kirin" hwd jî bên guherandin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:14, 19 gulan 2023 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:14, 19 gulan 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di ''Rêbera Rastnivîsînê'' ya Komxebata Kurmanciyê de (r. 80) dibêjin "'''[q]ya bêjeyên biyanî neke [k]'''". "k"ya vê peyvê jî bi eslê xwe "[q]" ye, û lewma divê awayê standard bibe "îqaz". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:22, 31 tebax 2023 (UTC)
== [[hundirîn]] ==
{{g|ku|hindirîn}}, {{g|ku|hundirîn}} standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:41, 27 gulan 2023 (UTC)
{{rfk|hundir|r=217}} Ji ber ku awayê "hundir" wek standard hatiye bijartin, li gor vê "hundirîn" jî dibe awayê standard. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:40, 29 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[wasita]] ==
{{g|ku|wasita}}, {{g|ku|wesite}}, {{g|ku|wasîte}}, {{g|ku|wasta}}, {{g|ku|wesita}}, {{g|en|wesita}}, {{g|en|wesîte}}, {{g|en|wasite}}
standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:06, 21 hezîran 2023 (UTC)
{{rfk|wasîte|r=504}}
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[qebe]] ==
{{g|ku|qebe}} / {{g|ku|qeba}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:10, 27 tebax 2023 (UTC)
{{rfk|}} --[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:35, 29 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min forma "qeba" baştir e ji bo halê standard. Helbet gava dîyarkirina halê standand divê em halên emilandinê û peyvên jêçêbûyî jî bifikirin. Ji bo vê peyvê wek "qebetî" / "qebatî". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:26, 29 çiriya pêşîn 2024 (UTC)
== [[sewgûr]] ==
{{g|ku|sewgur}} / {{g|en|sewgûr}} / {{g|en|sewgir}} / {{g|en|sewgor}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:33, 29 tebax 2023 (UTC)
{{rfk|sewgurî, sewgura sibê|r=439}} Di ferhengê de wek "sewgurî" û wek "sewgura sibê" hatiye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:33, 29 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[Danmarka]] ==
[[Danîmarka]] zêdetir tê bikaranîn--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:54, 3 îlon 2023 (UTC)
{{rfk|}} Di vê ferhengê de peyvên "danîmarkî" û "danîmarqî" ber bi rûpela "danmarkî" ve yê hatine alîkirin. Lewma mirov fam dike ku navê vî welatî li gorî vê ferhengê "'''Danmarka'''" ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:12, 3 îlon 2023 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] Bi min ew mantiqeke şaş e. Loma em li ferhengên din jî binêrin baştir be. Ez fêm nakim çawa ferhengeke rastnivîsînê navên welatan qeyd nekirine. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:27, 3 îlon 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi ya ''Rêbera Rastnivîsînê'' ya Komxebata Kurmancî jî awayê standard "'''Danîmarka'''" ye (r. 31). Ez bixwe jî ji bo awayê standard "Danîmarka"yê pêşniyar dikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:55, 3 îlon 2023 (UTC)
== [[dijmîn]] ==
{{g|ku|dijmîn}}, {{g|ku|dijûn}} Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:57, 12 îlon 2023 (UTC)
{{rfk|dijûn|r=135}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:04, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[fîlmografî]] ==
[[filmografî]]
:--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:38, 20 îlon 2023 (UTC)
{{rfk|fîlm|r=166}} Şiklê standard ji bo peyva "film / fîlm / fîlim" wek "fîlm" hatîye nîşandan. Lewma li gor vê awayê standard dibe "'''fîlmografî'''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:07, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[not]], [[nod]] ==
[[not]], [[nod]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:23, 11 çiriya pêşîn 2023 (UTC)
{{rfk|nod|r=338}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:09, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[afrîkî]] ==
[[afrîkî]] / [[afrîkayî]] ?--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:06, 9 çiriya paşîn 2023 (UTC)
{{rfk|afrîkayî|r=41}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:11, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[eware]] ==
[[eware]]/[[aware]]
:Bi maneya ''avare/aylak/serseri''
{{rfk|153}}
:Ji bo maneya ''penaber'' di vê ferhengê de standardek tine ye. Loma divê du rûpel hebe ji bo kelîmeya kurmancî ya eware û yekî din ji bo maneya penaber.
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:14, 8 sibat 2024 (UTC)
=== Gotûbêj ===
<!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di ferhenga ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de jixwe standarda ji bo vê peyvê heye. Ji bo maneya penaber jî hebûna vê maneyê bi min bişibhe ye, wekî din ''ger hebe jî'' ji ber ku ji berhirefkirina maneya "''yê derbeder û bê cih û mal''" hatîye çêkirin, dîsa girêdayî wê maneya eslî ya derbiderîyê ye, lewma divê li ser rûpelekî werin standardkirin û ew rûpel jî pêşnîyara di kitêba nav-zikirkirî de be baştir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:22, 18 hezîran 2024 (UTC)
== [[wesetî]] ==
[[wasatî]] / [[wesetî]] standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:48, 29 adar 2024 (UTC)
{{rfk|wesetî|r=506}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:27, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[se direwît, karwan bi rêka xwe diçît]] ==
{{g|ku|se direwît, karwan bi rêka xwe diçît}} / {{g|ku|se direwin, karwan diborin}} / {{g|ku|seh direye kerwan dibihore}} Divê ev her sê form werin standardkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:51, 16 hezîran 2024 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:26, 7 tîrmeh 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ya standardtir "'''seg direye karwan dibihure'''" ye ("seg", ''Ji Bo Rastnivîsînê Ferhenga Kurdî (Kurmancî) - Tirkî ya Nû'', r. 429; "reyîn", r. 410; "karwan", r. 240; "bihurîn", r. 83). Wekî form (di qismê "Herwiha" de) em "'''bi ewtîna seyan, ji rê namîne karwan'''" jî nîşan bidin. Lê ji alîyê xwedîçavkanîbûnê ve jî divê werin kontrolkirin. Min ji bo "''bi ewtîna seyan, ji rê namîne karwan''" çavkanî peyda nekir, min ji bo "''seg direye karwan dibihure''" jî tenê çavkanîyekê peyda kir ([https://www.tirejafrin.com/pdf/Pend%20.pdf ''Pend û Peng''], Bîlal Hesen, di kitêbê de wek "seh direye kerwan dibihore" derbas dibe). -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:26, 7 tîrmeh 2024 (UTC)
== [[serobino bûn]] ==
{{g|ku|serobino bûn}} / {{g|ku|serûbin bûn}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:21, 14 tebax 2024 (UTC)
{{rfk|serûbin|r=438}} "serûbin bûn, serûbin kirin, serûbinbûn, serûbinkirin, serûbinkirî" wek peyvên jêçêbûyî hatine nîşandayîn. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:19, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[sanî]] ==
[[sanî]] / [[saniye]]
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:45, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ez dibêjim ber saniye be. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:22, 1 îlon 2024 (UTC)
{{şîrove}} Ez dibêjim bila "sanîye" be. Em wek sanîye telafûz dikin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:45, 29 çiriya pêşîn 2024 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] "Sanîye" çênabe. Li vir em "îy" nanivîsînin. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 13:34, 10 çiriya paşîn 2024 (UTC)
== [[pêşnûma]] ==
[[pêşnûma]] / [[pêşnima]] Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:00, 29 çiriya pêşîn 2024 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:00, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min divê "pêşnima" be. Formên bi "u"yê zahfê caran nayên emilandin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:00, 29 çiriya pêşîn 2024 (UTC)
== [[payîz]] ==
[[payîz]] / [[payiz]]
Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:12, 29 çiriya pêşîn 2024 (UTC)
{{rfk|payîz|347}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:12, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[behîv]] ==
{{g|ku|behîv}} / {{g|ku|bahîv}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:41, 26 kanûna paşîn 2025 (UTC)
{{rfk|behîv|63}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:38, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[mitîng]] ==
[[mitîng]] / [[mîtîng]] Standardkirin hewce ye. -- --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 21:47, 27 kanûna paşîn 2025 (UTC)
{{rfk|mîtîng|r=316}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:43, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[rûtîn]] ==
[[rûtîn]], [[rutîn]] Standardkirin hewce ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:47, 18 sibat 2025 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:42, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şirove}} -->
{{şirove}} Bi min eşkere ye ku ya standard "'''rutîn'''" e. Peyvên telafûza wan dirêj be, bi "û"yê tên gotin, lê li vir dirêj nayê gotin. Lewma bi "u"ya bêkumik çêtir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:42, 1 hezîran 2025 (UTC)
== [[parleman]] ==
[[parleman]] / [[perleman]] / [[parlament]]/[[parlemento]] / [[perlemen]], Standardkirin hewce ye.--[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 00:53, 22 sibat 2025 (UTC)
{{rfk|parlamento|r=345}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:39, 1 hezîran 2025 (UTC)
=== Gotûbêj ===
{{şîrove}} Di ferhenga "Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû" da bi maneya ''meclis''ê wek "parlamento" hatîye qeydkirin, bi maneya ''azayê meclisê'' wek "parlamenter" hatîye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:39, 1 hezîran 2025 (UTC)
== [[sond]] ==
[[sond]], [[sûnd]]—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:41, 21 adar 2025 (UTC)
{{rfk|sond|r=447}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:35, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şirove}} -->
{{şirove}}
== [[sifet]] / [[sîfet]] ==
{{rfk|}}--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:41, 1 gulan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi dîtina min wek "sifet" zêdetir li kar e. (Ger rast transkrîbe kiribin) Di eserên berê yên kurdî de wek "sîfet" hatine gotin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:29, 31 gulan 2025 (UTC)
== [[mane]] ==
[[mane]], [[mana]]—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:13, 30 gulan 2025 (UTC)
{{rfk|mane|r=297}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:20, 31 gulan 2025 (UTC)
=== Gotûbêj ===
{{şirove}} {{r|Bikarhêner}} [https://ferheng.wmezopotamyaye.org/ weqfa mezopotamyaye] dibêje forma standard "mana" ye. Dikarî ji ferhenga rastnivîsînê binêrî? --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:18, 30 gulan 2025 (UTC)
: {{r|Wikihez}} Di Ferhenga Rastnivîsînê de wek "mane" (jê "mane dan", "manedan", "manedar", "manedarî") hatîye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:20, 31 gulan 2025 (UTC)
== [[memnû]] ==
{{g|ku|memnû}} {{g|ku|memnûh}} Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:34, 1 hezîran 2025 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:34, 1 hezîran 2025 (UTC)
=== Gotûbêj=== <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Eşkere ye ku ya standard "'''memnû'''" ye. Forma "memnûh" ne li kar e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:34, 1 hezîran 2025 (UTC)
== [[niha]] ==
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{rfk|niha|r=333}} [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 11:22, 16 sibat 2026 (UTC)
== [[gîya]] ==
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{rfk|gîya|r=180}} [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 11:24, 16 sibat 2026 (UTC)
Kelîmeya jêçêbûyî: {{g|ku|gîyagezk}} (mê, mane "''ısırgan otu''") -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:11, 5 tîrmeh 2026 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]], @[[Bikarhêner:Rewiye tine|Rewiye tine]] Bi ya min, ev ne meseleya standardkirinê ye lê ya îmlayê ye çûnkî ev problema îy - iy zêde tevlihev e. Dengdanek hewce ye ku bibêje konsensusek heye li ser bikaranîna -îy û ne -iy. Lê dengdanek wisa dê têk biçe ji ber ku hejmara kesên -îy tercih dikin kêmtir e li ser Wikiferhengê. Loma divê ev gotûbêj wek çênebû were tayînkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:04, 5 tîrmeh 2026 (UTC)
::Bi qenaeta min jî "'''îy'''" rasttir e. Divê em di demên pêş da wî meseleyê hel bikin. Feqet wekî ku te jî got, ji ber ku vê gavê li ser vê qerar nehatîye dayîn, em karin wek xwe bihêlin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:45, 6 tîrmeh 2026 (UTC)
== [[hincet]] ==
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{rfk|hincet|r=210}} [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 11:18, 16 sibat 2026 (UTC)
== [[kurmî]] ==
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{rfk|kurm (navdêr), kurmî (rengdêr)|r=269}} [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 08:55, 23 adar 2026 (UTC)
:kurmî berbelav e hem jî standardîze bûye bi min. Hem em dibêjin kurm bi gelemperî kirm kêm e. [[Bikarhêner:Bibliophile|Bibliophile]] ([[Gotûbêja bikarhêner:Bibliophile|gotûbêj]]) 10:55, 23 adar 2026 (UTC)
::Lê balkêş e ku rehê xwe yê qedîm zêdetir wekî [[kirm]] xuya dike. Lê etîmolojî nikare bikeve pêşberî bikaranîna giştî peyvekê ji aliyê xelkê ve. Helbet ev fikra min a şexsî ye. Ji bo selameta ferhengê kurdî ev yek gerek e bête çareserkirin. [[Bikarhêner:Bibliophile|Bibliophile]] ([[Gotûbêja bikarhêner:Bibliophile|gotûbêj]]) 11:01, 23 adar 2026 (UTC)
== [[tihn]] ==
[[tihn]], [[tîn]], [[tên]]
{{rfk|tîn (mê, mane: ''ısı; hararet, sıcaklık'')|r=488}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:18, 5 tîrmeh 2026 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
Ez bawer dikim ku ''tîn'' zêdetir tê bikaranîn.
== [[nehwirandin]] ==
[[nehwirandin]], [[norandin]]
bnr [[Gotûbêj:norandin]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:52, 3 tîrmeh 2026 (UTC)
{{rfk|nehwirandin|r=328}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:05, 5 tîrmeh 2026 (UTC)
=== Gotûbêj ===
== [[rus]], [[rûsî]] ==
[[rus]], [[rûs]], [[rûsî]], [[rusî]]
bnr. [[Gotûbêja kategoriyê:Rusî]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 07:11, 5 tîrmeh 2026 (UTC)
{{rfk|rûs, rûsî|r=422}} "rûs" wek navê miletê, "rûsî" wek navê zimanê hatîye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:57, 5 tîrmeh 2026 (UTC)
=== Gotûbêj ===
== [[dûkel]] ==
{{g|ku|dûkel}} / {{g|ku|dûkêl}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:14, 19 tîrmeh 2026 (UTC)
{{rfk|dûkel (nêr)|r=143}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:16, 19 tîrmeh 2026 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
30mgplxwx816mpap8qmiufz15bkyo66
6535041
6535040
2026-09-21T07:16:13Z
Kurdistanov
15290
/* Rojavayê Kurdistanê */
6535041
wikitext
text/x-wiki
{{/destpêk}}
= Daxwazên standardkirinê: 2020 =
== [[mizhar]] ==
mizhar / miz'ar / mizar --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:31, 10 tebax 2020 (UTC)
===Gotûbêj===
{{şîrove}}
Min gelek ferhangan nêrî lê min kelîmeyek wisa nedît. Divê ev kelîme were peyitandin. Wexta em kelîmeyek bo standartkirinê hilbijêrin divê hinek ser bisekinin. Ka kelîme hêjaye an na. Kelîmeyê zêde tên bikaranîn divê were hilbijêrîn.--[[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 20:54, 3 çiriya paşîn 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[kîte]] / [[kite]] ==
[[kîte]] / [[kite]] Divê werin standardkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:11, 13 tebax 2020 (UTC)
{{rfk||}} Di vê ferhengê de "kîte / kite" nîne, lê wek lêker cih dane "kîte kirin"ê. Jê tê famkirin ku halê standard wek "'''kîte'''" dibînin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:31, 28 nîsan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ji bo '''''[[kit]]''''' ez dibêjim '''''[[kit]]'''''; nabêjim '''''[[kît]]'''''. Ma '''''[[kîte]]''''' ('''''[[kît]]''''') li kû derê li kar e? -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 15:13, 5 îlon 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[hêvişandin]] ==
[[hêvişandin]] / [[êvişandin]] / [[hewişandin]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:50, 15 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[hindirandin]] ==
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 09:09, 16 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
*Bi ya min divê forma ''hundirandin'' bibe standard. Li herêma Behdînan gelek caran '''u''' dibe '''i''', '''û''' jî dibe '''î'''. Loma [[hindir]] weke rûpela sereke hatiye çêkirin.—<small>[[Bikarhêner:Gomada|<span style="background:Yellow">'''''<span style="font-family:Verdana;color:red">Gom<span style="color:#006400">ada</span></span></span>''''']]<sup>[[Gotûbêja bikarhêner:Gomada|peyam]]</sup></small> 15:40, 21 kanûna paşîn 2021 (UTC)
** {{silav|Gomada}}. Bnr. [[WF:Rêzikên standardkirinê]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:42, 21 kanûna paşîn 2021 (UTC)
== [[zingar]] ==
Eynî rûpel li [[zeng]] û [[jeng]] heye. Ez awayê rast wek [[zingar]] dibînim. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 12:34, 18 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[axaftin]] ==
[[axaftin]] / [[axivtin]] / [[axiftin]] / [[axivîn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:23, 19 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Axiftin û axivîn ne eynî tişt in. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:05, 22 tebax 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[jêbirk]] ==
[[jêbir]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:41, 19 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Xuya dike paşgira -k (bi vê maneyê) gelekî nayê bikaranîn. Agirkuj bi dêla agirkuj-k tê bikaranîn, min tenê "jêbir" bihîstîye, "xwêdan-k" bêçavkanî ye, "demjimêr-k" kêm peyda dibê (bêhtir di spam de heye) ... -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:39, 22 tebax 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[bager]] ==
[[bager]] / [[bagêr]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:16, 19 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[çikilandin]] ==
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 10:52, 20 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[evraz]] ==
[[evraz]] / [[hevraz]] / [[evrazî]] / [[hevrazî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:03, 20 tebax 2020 (UTC)
:ervaz [[Taybet:Beşdarî/~2026-16469-22|~2026-16469-22]] ([[Gotûbêja bikarhêner:~2026-16469-22|talk]]) 19:30, 15 adar 2026 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[xwişk]] ==
[[xwişk]] / [[xweng]] / [[xwek]] / [[xuşk]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:33, 21 tebax 2020 (UTC)
{{rfk|xwişk, xweng, xweh|r=524 (xweng, xweh), 526 (xwişk)}}
Di ferhengê de "xwişk (r. 526), xweng (r. 524), xweh
(r.524)" tev jî bi maneya "''kız kardeş''" hatine qeydkirin. "xwek" û "xuşk" jî tê de nîne. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:04, 2 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} ''[[xuşk]]'' u ''[[xweh]]'' ji ber ku di devoka min da herdu jî têne bikaranîn, wek ''[[xuçik]]'' u ''[[xweng]]''. ''[[xwek]]'' devokî ye u bipiranî li rojavayê tê bikaranîn. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 15:49, 21 tebax 2020 (UTC)
: {{silav|Hakîmo}} Kengî dengdana li ser [[Wîkîferheng:Rêzikên standardkirinê|Rêzikên standardkirinê]] xilas bû, em ê dest bi standardkirinê bikin. Tu jî dikarî fikrên xwe li vir bibêjî '''[[Wîkîferheng:Dengdan/Rêzik/Rêzikên standardkirinê]]'''.
::Di devoka min de xwîşk, xuh û xwak hene. Di wextên cuda de tên bikaranîn. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 16:06, 21 tebax 2020 (UTC)
Ez awayê rast ya xwîşk/xwişk weke "xwîşk" dibînim, û di înternetê de bêhtir encam hene (hejmara rûpelan binêrin, hejmara encaman şaş e.) -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:27, 25 tebax 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin.
{{erê}}: [[xuşk]] u [[xweh]] (belkî [[xuçik]] jî, lê niha [[xuşk]] ancax têr dikê)-->
:Silav hevalên hêja, ez nizanim çawa deng bidim, lê ji bo standartkirinê divê mirov binere kîjan peyv kevintir e, ji bo vê yekê ez dengê xwe didim peyva '''xweh''' [[Bikarhêner:Kurdistanov|Kurdistanov]] ([[Gotûbêja bikarhêner:Kurdistanov|gotûbêj]]) 06:59, 21 îlon 2026 (UTC)
== [[xwendevan]] ==
[[xwendevan]] / [[xwandevan]] / [[xwêndkarî]] / [[xwîndkarî]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:50, 21 tebax 2020 (UTC)
{{rfk|xwendevan, xwendekar|r=524}} Di ferhengê de bi maneyên cuda her du peyv jî derbas dibin. "xwendekar" bi maneya "öğrenci, talebe"; "xwendevan" bi maneya "okuyucu, okur" hatîye qeydkirin. Lewma divê her du jî hebin. "''xwêndkarî''" û "''xwîndkarî''" jî formên "xwendekarî"yê ne, lewma divê wek "xwendekarî" werin standardkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:52, 2 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Xwendevan û xwendekar bi eynî maneyî ne lê ne eynî peyv in, nikarin weke yek werin standardkirin. Ji ber ku xwendin/xwandin weke xwendin hatîye standardkirin divê xwendevan jî wilo bibê. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:05, 4 îlon 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[sefhe]] ==
[[sefhe]] / [[seyfe]] / [[sayfe]] -- [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 13:34, 22 tebax 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:06, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ya standard "sehîfe" ye. Forma "sefhe" di şiîrên Ehmedê Xanî de derbas dibe, lê îro ev peyv bi forma "sehîfe" tê zanîn. Lewma bi min divê em awayê standard wek "'''sehîfe'''" nîşan bidin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:06, 1 hezîran 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[pergala xebatê]] ==
[[pergala xebatê]] / [[pergala xebitandinê]]
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:29, 26 tebax 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:49, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ya rast ev ne pirseke li ser standardkirinê ye. Lewma li vir neyê minaqeşekirin baştir e. Lê dîsa fikrê xwe bêjim. Îngilîzîya wî "''operating system''" e, ew jî bi kurdî him dibe "''sîstema xebitandinê''" him jî "''sîstema xebitînê''", ji ber ku lêkera "operate" him transîtîv e him jî întransîtîv. Bi min "pergala xebatê" jî ne xelet e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:49, 1 hezîran 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[qet]] ==
Navdêra qet/qed (bi maneya parçe).
Ji erebî hatîye û ya erebî "qet" e (bi t-ya qalin weke '''t'''ewandin). Ya kurdî bi t-ya normal e. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 18:24, 28 tebax 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:02, 28 nîsan 2025 (UTC)
Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de 'qed' derbas nabe, qet bi maneya 'hîç' derbas dibe, halê standard ya 'qet'a bi maneya 'parçe'yê wekî 'qut' nîşan dane. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:07, 28 nîsan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
* Hiii, min got yanî ya hoker bû. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 14:03, 29 tebax 2020 (UTC)
:{{r|Hakîmo}} Min şablonê li navdêrê danîbû lê baştir e ez binivîsim ku li ser navdêrê ye. (Min vêce nivîsî). -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:30, 29 tebax 2020 (UTC)
{{şîrove}} Wek min li jor nivîsî, ev peyv di ferhenga ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de tine ye, feqet bi min divê forma "'''qet'''" wek ya standard were qebûlkirin. Wek notek biçûk vê jî bêjim, ji "qet"a bi maneya "parçe" lêkera "qet kirin"ê jî çêbûye û lêkera "qut kirin"ê ku tê maneya "du tiştan ji hev qet kirin" û lêkera "qet kirin"ê bi eynî maneyê ne. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:02, 28 nîsan 2025 (UTC)
{{şîrove}} Li ser meseleya 'qet' û 'qut'ê jî, her du jî pir berbelav in, lewma bi min divê her du jî bimînin. Wekî din hin ferq jî hene di navbera wan de, wek nimûne, halê navdêrî ya "qet"ê berbelav e, lê halê navdêrî ya "qut"ê neberbelav e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:07, 28 nîsan 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[darberû]] / [[darberî]] ==
[[darberû]] / [[darberî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:36, 30 tebax 2020 (UTC)
{{rfk|berû|r=73}} Peyva "darberû" nîne, lê "berû" heye. Jixwe peyda "dar" standard e, nelihevî li ser qismê "berû" û "berî"yê ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:24, 27 nîsan 2025 (UTC)
{{ds-kirî|darberû}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:06, 1 hezîran 2025 (UTC)
== [[diran]] / [[didan]] ==
[[diran]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:52, 31 tebax 2020 (UTC)
{{rfk|diran|r=136}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:20, 27 nîsan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} '''''[[didan]]''''' awayê standard e ji ber ku '''''[[diran]]''''' ji wê hatiye: '''''didan''''' > '''''diran'''''
* Li Qonyayê herdu li kar in (ez '''''[[didan]]''''' bi kar tînim); li rojhilatê '''''[[didan]]''''' pirtir li kar e; Ehmedê Xanî di ''Nûbihara Biçûkan'' da '''''[[didan]]''''' bi kar anîbû. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 08:29, 1 îlon 2020 (UTC)
* {{erê}} [[didan]] -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 08:29, 1 îlon 2020 (UTC)
Em dibêjin diran û dinan. "Didan" formeke lêkerê ye jî ("ew xwarin ''didan'' xelkê"), baştir e ne weke hev bin.
Diran (înternetê bigere "diranê" û "diranên") gelekî bêhtir ji "didan" ("didanê", "didanên") tê bikaranîn.
Fikirên [[Bikarhêner:Ferhengvan|Ferhengvan]] (23ê îlonê 2019) li vir hene: [[Gotûbêja pêvekê:fînî-kurdî]]. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:02, 1 îlon 2020 (UTC)
{{şîrove}} {{r|Guherto|Hakîmo}} Hevalno hûn tim behsa bilêvkirinê dikim lê hûn ti carî behsa çavkaniyan nakin. Binêrin [[Wîkîferheng:Dengdan/Rêzik/Rêzikên standardkirinê]] dibêje ''Derbarê awayê peyvekê de çiqas zêde çavkaniyên kitêb an ferhengên rastnivîsînê, ferheng û kovarên zimannasiyê hebin, ew awa wek awayê standard tên pejirandin.'' Yanî bilêvkirina xelkê ne xema me ye. Heta niha 4 kes gotine erê, ku hûn naxwazin em bi ya çavkaniyan bikin hingê dengê xwe bidin, bibêjin na daku ew rêzik nayên pêjirandin û em dîsa gotûbêj çêkin, rêzikên din hilbijêrin. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:11, 1 îlon 2020 (UTC)
::{{r|Balyozxane}} Min gotibû ''Diran (înternetê bigere "diranê" û "diranên") gelekî bêhtir ji "didan" ("didanê", "didanên") tê bikaranîn.''. Ew li ser çavkanîya ne. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:26, 1 îlon 2020 (UTC)
:::{{r|Guherto}} Hingê çima dengê xwe nadî? --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:27, 1 îlon 2020 (UTC)
::::{{r|Balyozxane}} Min hin sanîye berî ku tu vê peyama xwe ya talî şand dengê xwe da :) -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:28, 1 îlon 2020 (UTC)
:::::{{r|Guherto}} Mala te ava! :) --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:30, 1 îlon 2020 (UTC)
::::::{{r|Balyozxane}} Merheba. Min berê jî gotibû: ''rewşa kurmancên me gelek xirab e, u ji ber hindê pir kurmancên me bi xwendin u nivîsîna zimanê xwe nizanin''. Çavkaniyên niha bipiranî ji devokên navendî tên, hinên rojhilatê jî bi herfên erebî ne. Zimanê standard bi çavkaniyan çênabit, divêt li gorî zimanê hemî kurmancan çêbit. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 16:19, 2 îlon 2020 (UTC)
::::::{{r|Balyozxane}} Herwiha '''''[[didan]]''''' helbet dikarit wek '''''[[diran]]''''' bi lêv bibit, lê berevajî nabit. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 16:22, 2 îlon 2020 (UTC)
:::::::{{r|Hakîmo}} Merheba, fikirên xwe li dengdanê çima nadê? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 16:44, 2 îlon 2020 (UTC)
::::::::{{r|Guherto}} Ne min dayê... -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 12:53, 3 îlon 2020 (UTC)
Ez dibêjim ku "dinan" formêka rasttirin bi kurdî ye (bi kelhûrî bikar tînin). Em berhevdin:
Farsî: duzd, nazdīk, gandum, gand, dandān, xand-īd-an
Kurdî: diz, nezîk, genim, genî, dinan, ken-î-n
Zazayî: dızd, nezdi, genım*, gende, dından, (hûwaene)
Bi kurmancî, soranî u kelhûrî ZD u ND dibin Z u N, D dikeve. Ev qeyde gelêk kevin e.
*ji kurmancî, bi taliş qender: gāndım
[[Bikarhêner:Umut Akkoç|Umut Akkoç]] ([[Gotûbêja bikarhêner:Umut Akkoç|gotûbêj]]) 14:14, 18 gulan 2021 (UTC)
{{ds-kirî|diran}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:07, 1 hezîran 2025 (UTC)
== [[dur]] ==
[[dur]] û [[dir]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:41, 31 tebax 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[nanoziko]] ==
[[nanoziko]] / [[nanzikî]] / [[nano ziko]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 06:57, 4 îlon 2020 (UTC)
{{rfk|nanozikî|r=324}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:27, 27 nîsan 2025 (UTC)
{{ds-kirî|nanozikî}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:07, 1 hezîran 2025 (UTC)
== [[sînor]] / [[sinor]] ==
sînor / sinor
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:15, 4 îlon 2020 (UTC)
{{rfk|sînor|r=446}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:25, 27 nîsan 2025 (UTC)
{{ds-kirî|sînor}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:08, 1 hezîran 2025 (UTC)
== [[ard]] ==
ard / ar -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:10, 6 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:52, 27 nîsan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} ard rasttir dibînim -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:10, 6 îlon 2020 (UTC)
{{şîrove}} "ar" tê maneya "agir"ê jî. "Erd" (rûyê zemînê) û "ard" jî wek telafûz nêzî hev in. Di peyva "arvan"ê de jî wek "ar" derbas dibe. Bi min wek "'''ar'''" baştir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:52, 27 nîsan 2025 (UTC)
{{şîrove}} ard rasttir e [[Bikarhêner:Kurdistanov|Kurdistanov]] ([[Gotûbêja bikarhêner:Kurdistanov|gotûbêj]]) 07:08, 21 îlon 2026 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[ajne]] /[[avjenî]]/[[ajnî]] ==
[[ajnî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:05, 6 îlon 2020 (UTC)
* {{rfk|ajne, avjenî|r=42, 50}} Di vê rêbera rastnivîsînê de her du formên wek "ajne" û "avjenî" wek standard hatine qeydkirin. Lewma formên peyva ''ajne'' yên wek "ajnê", "ajnî", "aşnê" divê li madeya "ajne"yê werin alîkirin, lê "avjenî" bi serê xwe cih bigire. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:39, 21 îlon 2024 (UTC)
{{ds-kirî|[[avjenî]], [[ajne]]}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:09, 1 hezîran 2025 (UTC)
== [[gurî]] ==
[[gurî]] / [[gurîtî]] / [[gurriyatî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:53, 6 îlon 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[parsî]] ==
[[partî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:09, 7 îlon 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[xwestin]] ==
xwestin / xwastin
xwestin rasttir dibînim. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 18:09, 9 îlon 2020 (UTC)
{{rfk|xwastin|r=523}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:48, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ez jî '''''[[xwestin]]''''' rasttir dibînim; jixwe '''''[[xwastin]]''''' bi min giran têt, o '''''[[xwestin]]''''' di hemî lehceyên kirmancî da heye. -- [[Bikarhêner:Hakîmo|Hakîmo]] ([[Gotûbêja bikarhêner:Hakîmo|gotûbêj]]) 08:21, 19 îlon 2020 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[îrrasyonel]] ==
[[îrrasyonal]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:44, 9 îlon 2020 (UTC)
{{rfk||}} Ev peyv derbas nabe, feqet peyva "rasyonel" wek standard cih girtîye (r. 403). Lewma ev jî dibe "'''îrrasyonel'''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:46, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[ajel]]/[[ajal]] ==
ajel / ajal
Em dibêjin heywan :) -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:11, 10 îlon 2020 (UTC)
* {{rfk|ajel|r=42}} --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:34, 22 îlon 2023 (UTC)
=== Gotûbêj ===
<!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Peyva ajel ji peyva sewal a kurmancî hatiye standin (s>J) sewal anku ajel ew canewerên çarpayî yên kedîkirî ne. Wekî pez, bizin, çêlek.{{bêîmze|46.161.196.225}}
{{ds-kirî|ajel}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:11, 1 hezîran 2025 (UTC)
== [[pîrebok]]/[[pîrhebok]] ==
{{rfk|pîrebok|r=365}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:44, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ji bo Rastnivîsnê Ferhanga Kurdî; di pera 365 wek ''pîrebok'' hatiye standardîzekirin. Di ferhengê de herweha ev form jî hene: pîraboçk, pîrabok, pîraboşk, pîrabotîk, pîravok, pîravtong, pîreboşk, pîrebotk.
Wîkîferhengê herwiha forma pîrhebok heye[[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 21:15, 10 îlon 2020 (UTC)
{{ds-kirî|pîrebok}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:11, 1 hezîran 2025 (UTC)
== [[şirove]] / [[şîrove]] ==
[[şirove]] / [[şîrove]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:50, 11 îlon 2020 (UTC)
{{rfk|şirove|r=466}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:41, 6 adar 2025 (UTC)
{{ds-kirî|şirove}}--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 08:48, 5 tîrmeh 2026 (UTC)
== [[pezkûvî]]/[[pezkovî]] ==
[[pezkovî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 08:59, 11 îlon 2020 (UTC)
{{rfk|pezkûvî|r=354}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:41, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{r|Balyozxane}} Yek hatîye jêbirin. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:22, 23 çiriya pêşîn 2020 (UTC)
{{ds-kirî|pezkûvî}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:13, 1 hezîran 2025 (UTC)
== [[dibistana seretayî]]/[[dibistana seratayî]] ==
[[dibistana seratayî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:22, 12 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:39, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min eşkere ye ku awayê standard "sereta" ye, lewma ya standard dibe "''[[dibistana seretayî]]''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:39, 6 adar 2025 (UTC)
{{ds-kirî|dibistana seretayî}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:13, 1 hezîran 2025 (UTC)
== [[sêzdeh]]/ [[sêzde]] ==
/ sêzde.
sêzdeh rasttir dibînim, sê + z + deh. Ti sedem nîne ji bo jêbirina "h" -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:34, 13 îlon 2020 (UTC)
{{rfk|sêzdeh|r=441}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:37, 6 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ferhenga Michael L. Chyet (2003) sêzde ber alî dike sêzdeh. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:34, 13 îlon 2020 (UTC)
: yazdeh/yazde (beralîkirin), çardeh/çarde (dubare), pazdeh/pazde (beralîkirin), şazdeh/şazde (beralîkirin), hevdeh/hevde (beralîkirin), hejdeh/hejde (beralîkirin), nozdeh/nozde (beralîkirin) -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:37, 13 îlon 2020 (UTC)
{{ds-kirî|sêzdeh}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:14, 1 hezîran 2025 (UTC)
== [[çirûsk]]/[[çirîsk]] ==
çirîsk/çirûsk
çirûsk(m):1.kıvılcım, çıngı
çirîsk(m): binêre(çirûsk)(per 116,117)[[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 22:46, 13 îlon 2020 (UTC)
{{rfk|çirûsk|r=116}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:36, 6 adar 2025 (UTC)
{{ds-kirî|çirûsk}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:15, 1 hezîran 2025 (UTC)
== [[êriş]]/[[êrîş]] ==
[[êriş]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:11, 15 îlon 2020 (UTC)
* {{rfk|êriş|r=157}} --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:36, 22 îlon 2023 (UTC)
{{ds-kirî|êriş}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:15, 1 hezîran 2025 (UTC)
== [[mûmin]] ==
[[momîn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:22, 16 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:00, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ya standard "'''mumin'''" e. Bi apostrof baştir be jî, ji ber nestandardbûna emilandina apostrofan, ji bo halê standard bêapostrof baştir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:00, 5 sibat 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[xwarza]] ==
[[xwarza]] / [[xwarzî]] / [[xuharzî]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 15:14, 17 îlon 2020 (UTC)
{{rfk|xwarza, xwarzî|r=523}} Her du peyv jî bi eynî maneyê di ferhengê de cih girtine. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:34, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[lênêrîn]] ==
[[nerîn]] / [[nêrîn]] / [[lênêrîn]] / [[lê nerîn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:19, 17 îlon 2020 (UTC)
{{rfk|nêrîn|r=333}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:04, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} "Lênêrîn" jixwe wek navdêr "''nêrîna li tiştekî''" ye, û "lê nêrîn" jî wek lêker "''li tiştekî nêrîn''" e, lê belê di her dukan de jî lêker/navdêr "nêrîn" e. "li"ya pêşî ji bo maneya "nêrîn"ê bêtesîr e/tesîreke wê lê nîne. Feqet ger were gotin ku "nêrîn" (bi îngilîzî "looking") wek forma "lênêrîn" xwedî maneyeke din e (ku divê ev yek jî bi çavkanîyan were îsbatkirin) wê gavê mirov kare wê peyvê cardin binirxîne. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:04, 5 sibat 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[hewar]] ==
[[hewar]]/[[hawar]] --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 10:07, 19 îlon 2020 (UTC)
{{rfk|hawar|r=190}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:35, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[muhakeme]] ==
[[muhakeme]] / [[mihakeme]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:59, 19 îlon 2020 (UTC)
{{rfk|mihakeme|r=309}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:34, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[tevabûn]] ==
[[tewabûn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:22, 19 îlon 2020 (UTC)
{{rfk|temambûn|r=477}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:32, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Tev jî formên "''temambûn''"ê ne. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:32, 5 sibat 2025 (UTC)
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[fiêl]] ==
[[fiêl]] / [[fîil]] / [[fîîl]] Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:12, 19 îlon 2020 (UTC)
{{rfk|fêl|r=164}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:21, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
<!--
=== Dengdan ===
{{dengdan/nû|destpêk=?|dawî=?}}
piştî dengdan dest pê kir vê şablonê {{dengdan|destpêk=|dawî=}}, û kengî xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
== [[pêşniyazkirî]] ==
[[pêşniyarkirî]], [[pêşniyar]] beralî dike [[pêşniyaz]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:24, 19 îlon 2020 (UTC)
'''Eynî mijar li jêr hatîye vekirin. Ji bo standardkirinê binêrin ya jêr.''' -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:18, 5 sibat 2025 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[xwehr]] ==
[[xwehr]] / [[xwar]] Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:43, 20 îlon 2020 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Bikarhêner}} Kîjan mane? Jixwe ya xwehr (îng crooked) standardkirî bû lê te xwehr kopî kir xwar. Çima? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:07, 20 îlon 2020 (UTC)
: {{r|Guherto}} Merheba bira. Li ser rûpela "[[xwar]]" maneya rengdêrî ya "ne rast, tewandî" tinebû. Tenê maneyeke wek "çewt, xelet" ''(bi cureyê rengdêrî)'' hebû. Ji ber ku mane kêm û şaş hatibû dayîn min ji rûpela "[[xwehr]]" mane û beşên din kopî kir û bi rûpela "[[xwar]]" ve kir. Û ji ber ku ji bo tenê maneyekê du awayên nivîsê yê wek "[[xwar]]" û "[[xwehr]]" wek standard cih digirtin, min şablona standardkirinê tevlî wan her du rûpelan jî kir, û ji bo ku em awayê standard bibijêrin li vir beşekê nû vekir. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:25, 20 îlon 2020 (UTC)
::Tu dikarîbû bnr-ek lê zêde bikê, ne hewce ye kopîkirin. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 09:48, 20 îlon 2020 (UTC)
::: {{r|Guherto}} Ji ber ku ez awayê standard wek "[[xwar]]" dibînim min rûpelê wek standard hazir kir. Awayê bnr-yî ne awayê wek standard e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:07, 20 îlon 2020 (UTC)
::::Temam. Lê hêjî kopîkirin ne hewce ye, bnr min çawa kir li [[#girover]] ji ber ku ez wê standard didîtim (dibînim). -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 10:11, 20 îlon 2020 (UTC)
::::: {{r|Guherto}} Baş e, di yên din de êdî ez ê wisa bikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:35, 20 îlon 2020 (UTC)
xwehr = ne rast (tirkî:eğik) û xwar = jêr (tirkî:aşağı).—<small>[[Bikarhêner:Gomada|<span style="background:Yellow">'''''<span style="font-family:Verdana;color:red">Gom<span style="color:#006400">ada</span></span></span>''''']]<sup>[[Gotûbêja bikarhêner:Gomada|peyam]]</sup></small> 15:34, 21 kanûna paşîn 2021 (UTC)
{{şirove}} Birêzan; li herêma me ev peyv wek ''xar'' tê bilêv kirin û li serhedê heta Sêwaz û ji wir hata Rojavayê Kurdistan bi vî awayîye.Min xwehr heya niha ji devê kesî nebihîstibû. Bi ya min xwar rasttire. Ji ber ku li devê meda di nav gellek peyvanda tîpa ''w'' nîne. Lê îjar jî bi peyva ''xwarin'' û ''xwarê''(berjêr) tevlihevî çêdibe.Dema ku standardizasyon çêdibe divê hemî peyvên watecûda bi wekhev neyê nivîsandin. Wek mînak niha peyvên kêr(alav,feyde,pozdewixî), ker(ajal,parçe,koma pez,nebihîz) 3-4 peyven ku xwendinên wan cûdane bi heman awayî tê nivîsin,ev zimanê me zortir dike û dibe sedema xeletîyan.Divê ziman standardîze be, lê ne bi vî awayî. {{bêîmze|Mihemed Qers}}
== [[dirist]] ==
[[dirist]] / [[dirust]]
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 13:44, 20 îlon 2020 (UTC)
{{rfk|dirust|r=137}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:15, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[neynok]] ==
[[neynok]] / [[neynûk]] / [[nînok]] / [[nênûk]] / [[nenîk]]
Awayê ku herî kêm tê bikaranîn (nînok) weke standard hatîye neqandin, ez vê şaş dibînim. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:43, 20 îlon 2020 (UTC)
Eynî rûpel li nînok û nênûk hene. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:45, 20 îlon 2020 (UTC)
{{rfk|nenûk|r=328}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:10, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi qenaeta min divê halê standard "neynûk" be. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:10, 5 sibat 2025 (UTC)
== [[neynokbir]] ==
neynokbir/neynûkbir/nenîkbir
Divê li gora encama [[#neynok]] werê standardkirin. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:39, 20 îlon 2020 (UTC)
{{rfk|nenûk|r=328}} Di vê ferhengê de ''nenûkbir'' nîne, lê ji bo peyva "neynûk"ê awayê standard wek "nenûk" hatîye nîşandayîn. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:12, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min wek halê standard forma "neynûk" baştir e. Lewma ev jî dibe "''neynûkbir''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:12, 5 sibat 2025 (UTC)
== [[xwijok]] ==
[[xişok]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 02:46, 21 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:44, 5 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ji ber ku halê wê yê lêker wek "''xişîn''" e, ya standard "'''xişok'''" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:44, 5 sibat 2025 (UTC)
== [[pirhindik]] ==
[[pir hindik]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:34, 22 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:30, 3 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Formeke wek "pirhindik" bi min bêçavkanî ye. Lewma ya standard divê wek "pir hindik" be. Feqet peyva "pir hindik" jî bi maneya "teqrîben, nêzîkî" nîne. Divê ew qism jî were rastkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:30, 3 sibat 2025 (UTC)
:Min maneya wê fam kir, feqet nizanim, emilandineke wisa heye an tine. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:26, 6 adar 2025 (UTC)
== [[qireqir]] ==
[[qîreqîr]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:22, 23 îlon 2020 (UTC)
{{rfk|qîreqîr|r=390}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:11, 3 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[pênivîs]] ==
[[pênivîs]] / [[pênûs]] / [[pênivîsk]].
Pênivîs bêhtir tê bikaranîn. Ev û pênûs eynî peyv in, lê pênûs li gora "nûsîn"a soranî û pênivîs li gora "nivîsîn"a kurmancî hatîye çêkirin. Em wan bikin yek?
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:58, 24 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:34, 3 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
# pênivîs - Ferhenga Birûskî Michael L. Chyet
# pênivîs - Ferhenga Destî çap 2 Umîd Demîrhan
# pênivîs - Ferhenga Kurdî ( Kurmancî ) - Farsî. Koma Kurdîya Kurmancî. Azad MAKÛYÎ,Haşim MEHMÛDÎ,Şehnaz OMERZADE, Nasir ECEMÎ,Mohsin QULÎZADE,Kejal PADAŞ,Efsûn YÛSIFÎ
# pênivîsk - Rehnas Husein Muhammed
{{şîrove}} Qertafa "-k"yê di çêkirina navdêran de li kar e. Lewma ji alîyê xisûsîyeta navderbûnê ve "''pênivîsk''", ji alîyê pratîkbûnê ve ''"pênivîs"'' baştir e. Lê eger krîtera girêdayîmayîna bi eslê peyvê ve were esasgirtin, wê gavê jî ''"pênûs"'' baştir e. Biqenaeta min "'''pênivîsk'''".
: Ji xeynî vê qismeke ji [https://zimannas.wordpress.com/2015/12/21/em-cima-kurdi-li-ber-xwe-dikin-ciyayeki-ase/ nivîsa malpera Zimannasê]:
: ''[Peyva ”qelem” ji aliyê hemû kurdan ve û ji hêla miletên misilman ve ji Indonezyayê heta Mexribê tê fehmkirin û bikaranîn. Lê nivîskarên me qîma xwe bi wê naînin ji ber ku bi xeletî bawer dikin ku eslê wê erebî ye. Lê eslê wê jî yûnaniya kevn e: κάλαμος (kálamos). Li cihê wê nivîskarên me xwe dispêrin peyvên çêkirok yên wek ”pênivîs(k)” yan jî ”pênûs” ya ji soranî wergirtî. Lê ”pênivîs(k)” yan ”pênûs” her tişt e ku mirov pê dinivîse, ne tenê qelem e: li vê serdemê mirov zêdetir ne bi qeleman lê bi kompûterê, bi telefona mobîl yan bi hin makîneyên din dinivîse. Amûrên nivîsînê yên serekî niha ne qelem in lê kompûter, laptop, tablet û telefon in. Li gor vê rêbazê be, divê mirov ji kompûteran, ji laptopan re, ji tabletan re û ji telefonan re jî bibêje ”pênivîsk/pênûs”, ne tenê ji qeleman re!]'' -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:34, 3 sibat 2025 (UTC)
== [[behs]] ==
/ [[bas]].
Em dibêjin beḧs. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:49, 24 îlon 2020 (UTC)
{{rfk|behs|r=64}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:42, 1 sibat 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[dîmender]] ==
[[dîmender]] / [[dîmendêr]].
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:43, 25 îlon 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:45, 27 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Paşgira "der" / "dêr"ê li şûna hev hatine emilandin. Lê ji nimûneyên jêrîn ên ji ferhenga ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' tê dîtin ku qerareke li ser zelalkirina halê standard nehatîye dayîn di wê ferhengê de.
Bnr. [[agahîder]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 41), [[hander]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 189), [[kêfder]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 253),
[[avdêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû wek "avdêr" nîne, lê wek "''avdêrî''" r. 50), [[bangdêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 58), [[dengdêr]] ''-ne zelal e, nimûneyên din fahmbartir in-'' (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 127), [[kardêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 239), [[saloxdêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 425), [[serîhildêr]] (Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû r. 435),
[[xwepêşander]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne), [[dersder]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne), [[fermandêr]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de ev form nîne, wek "''fermande''" qeyd kirine), [[deynder]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne), [[girêdêr]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne), [[mizgînder]] (Di ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de nîne).
Bi min divê em li balê bidin ser ku paşgirên "der" / "dêr"ê eynî maneyê didin an na. Bi min paşgira "der"ê qismen nêzî paşgira "-dar"ê ye ku ew jî maneya xwedîbûna tiştekî dide. Lewma bi min ji bo peyvên bi maneya "kesê tiştekî dide" emilandina paşgira "-dêr"ê wê baştir be, lê ger em ji alîyê rehetîya telafûzê bifikirin jî paşgira "-der"ê baştir e ji bo awayê standard. Wek netîce bi min ji bo halê standard (ji bo peyvên bi maneya "kesê tiştekî dide") paşgira "-dêr"ê başttir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:45, 27 kanûna paşîn 2025 (UTC)
== [[Rojavayê Kurdistanê]] ==
/Rojavaya Kurdistanê.
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 12:16, 26 îlon 2020 (UTC)
[[Rojavayê Kurdistanê]]-- [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 21:12, 9 adar 2021 (UTC)
{{rfk|"rojava" wek nêr derbas dibe|r=416}} Peyva "rojava" wek nêr qeyd kirine. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:13, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} ''{{g|ku|rojava}}'' navdêreke mê ye, li gori rêzimana kurdi nivîsa rast "Rojava'''ya''' ''(îzafe)'' Kurdistan'''ê''' ''(çemandî)''" ye. Feqet "Rojavayê Kurdistanê" jî pirr tê bikaranîn.. --[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:27, 9 adar 2021 (UTC)
{{şîrove}} Slav @[[Bikarhêner:Ghybu|Ghybu]], gelle me û mezinên me dema ku peyvan bilêv dikin nêr/mê bûna peyvan çîye nizanin,peyv û hevokên ku hezaran salanda terkîb bûne û derketine holê bi xwezayî ji devên wan dertê, heta eger ku ji wanra nêr/mê bûna peyvan qal bikin yê li me xerîb binêrin,lê wan bi hezaran saland a bê qest û bi xwezayî vî zimanî qasê ku meriva şaş bihêle bi duzen ava kirine. Ne tenê herêma me, min ji gellê çi bihîstîye wê dinivîsim. Lê ji bilî gellê meyê hin herêmên Mêrdînê min tu cîyek nebihîstye ku mînak şûna ″porê min″ ″pora min″,yan jî şûna ″Rojavayê Kurdistanê″ ″Rojavaya Kurdistanê″ bêjin. Lê bi ya min em nikarin bêjin jiber ku peyva ″roj″ mê ye divê ″rojava″ jî mê be. Rast e rojava ji roj tê lê edî rojava peyveke cûda ye. [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 23:09, 9 adar 2021 (UTC)
{{şîrove}} Di Rêbera Rastnivîsînê ya Komxebata Kurmancîyê de (r.40) jî wek alî (wek hêl, ne parçe) "'''rojavayê''' Kurdistanê" derbas kirine. Ji ber ku eynî peyv e, em dibînin ku li wir jî wek "nêr" qebûl kirine. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:13, 26 kanûna paşîn 2025 (UTC)
{{şîrove}} rojava di hemû pirtûk û ferhengên ez nas dikim peyveke nêr e. Ji bo wilo '''Rojavayê Kurdistanê''' rast e [[Bikarhêner:Kurdistanov|Kurdistanov]] ([[Gotûbêja bikarhêner:Kurdistanov|gotûbêj]]) 07:15, 21 îlon 2026 (UTC)
== [[diz]] ==
[[diz]] / [[dizîker]] / [[dizîkar]]. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 12:37, 27 îlon 2020 (UTC)
{{rfk|diz, dizek|r=138}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:06, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} ″diz″ ji xwe kesek e ″dizî″ jî karesata wî/ê ye lewma ″dizîker/dizîkar″ xeletin.[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 20:38, 9 adar 2021 (UTC)
:Kes nabêje "dizîker,/dizîkar", kurd dibêjin : dizek . [[Taybet:Beşdarî/83.252.83.100|83.252.83.100]] 19:25, 3 gulan 2023 (UTC)
{{şîrove}} Şiroveyên hatî yên berê ji bo min jî rast dixuyin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:06, 26 kanûna paşîn 2025 (UTC)
== [[rêkxistin]] ==
[[rêxistin]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:03, 27 îlon 2020 (UTC)
*{{rfk|rêxistin}}--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:27, 23 îlon 2023 (UTC)
=== Gotûbêj ===
<!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} [[rêxistin]] -- [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 20:39, 9 adar 2021 (UTC)
== [[cihêl]] ==
[[cihêl]] / [[cehêl]] standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:38, 29 îlon 2020 (UTC)
{{rfk|cahil|r=94}} Forma standard wek "'''cahil'''" hatîye nîşandan, ji bo kelîmeya "cihêl". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:10, 4 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[tihn]] ==
[[tên]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:41, 30 îlon 2020 (UTC)
{{rfk|tîn|r=488}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:01, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[gişt]] ==
[[gişt]] / [[giş]] / [[gî]] / [[gîh]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:25, 30 îlon 2020 (UTC)
{{rfk|gişt|r=179}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:55, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[tal]] ==
[[tehl]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:35, 1 çiriya pêşîn 2020 (UTC)
{{rfk|tahl|r=472}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:51, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Birêzan pirranîya gellê me vê peyvê wek ″تعال″ bilêv dike lê alfabeya me da tîpa ″ع″ tune.Divê em jibo vê tîpê standartekî çêkin, wek şûna ″ع″ ″a yan jî eh″ bikarbînin wê demê pirsgirêk yê jibo peyvên din jî çareser be. -- [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 21:31, 9 adar 2021 (UTC)
== [[şuxl]] ==
[[şol]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 02:56, 2 çiriya pêşîn 2020 (UTC)
[[şuxil]]--[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 21:41, 9 adar 2021 (UTC)
{{rfk|şixul|r=466}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:50, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[gilîz]] ==
[[girêz]] / [[gilîz]] / [[gilêz]] -- [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:25, 5 çiriya pêşîn 2020 (UTC)
:girêz [[Taybet:Beşdarî/~2026-20662-33|~2026-20662-33]] ([[Gotûbêja bikarhêner:~2026-20662-33|talk]]) 19:33, 3 nîsan 2026 (UTC)
{{rfk|gilêz|r=176}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:46, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Çi wê ê standardkirin? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:27, 14 çiriya paşîn 2020 (UTC)
{{şîrove}} [[girêz]] -- [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 20:42, 9 adar 2021 (UTC)
== [[waris]] ==
/ [[wêris]]
-- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 17:03, 6 çiriya pêşîn 2020 (UTC)
{{rfk|waris|r=504}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:00, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[rêveber]] ==
/birêveber. Ber alî dike "birêveber" lê bahwer im "rêveber" bêhtir tê bikaranîn. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:50, 12 çiriya pêşîn 2020 (UTC)
{{rfk|birêveber|r=86}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:15, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min her çiqas lêker "''bi rê ve birin''" be jî halê standard wek "''rêveber''" be baştir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:15, 26 kanûna paşîn 2025 (UTC)
== [[jêk cuda bûn]] ==
/jê cuda bûn. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:15, 15 çiriya pêşîn 2020 (UTC)
{{rfk|ji hev cihê bûn, ji hev cuda bûn|r=230}} Forma "jêk" (ji yek) di vê ferhengê de derbas nabe. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:13, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} [[jê cuda bûn]]--[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 18:18, 7 adar 2021 (UTC)
{{şîrove}} Di "Cuda bûn" / "cihê bûn"ê de jixwe du tişt ji hev cuda dibin, lewma bi min madeyeke wek "''jê cuda bûn''" / "''jê cihê bûn''" (ji tiştekî cuda bûn / ji tiştekî cihê bûn) nehewce (neferhengî) ye. Forma "''jêk''" ''(ji yek)'' di ferhenga 'Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû' de derbas nabe.
Lewma bi dîtina min her du formên mewcûd jî nestandard in, lê belê ya standard "'''''ji hev cuda bûn'''''" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 15:13, 26 kanûna paşîn 2025 (UTC)
== [[pakêt]] ==
/paket. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 19:04, 24 çiriya pêşîn 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:56, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû de (r. 346) ji bo halê standard yê kelîmeya "''parsêl''"ê "''parsel''" nîşan dane. Ev jî wekî wî ye, '''lewma halê standard "''paket''" e''', lê ne "''pakêt''". Ji ber ku telafûza "''pakêt''"ê jî li gor yê din zehmettir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:56, 26 kanûna paşîn 2025 (UTC)
== [[hevaltî]] ==
/ [[hevalî]]. Hevaltî rasttir e û bêhtir tê bikaranîn. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 21:32, 31 çiriya pêşîn 2020 (UTC)
{{rfk|hevalî, hevaltî|r=200}} Di vê ferhengê de her du formên wek "''hevaltî''" û "''hevalî''" jî wek standard hatine qebûlkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:52, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min divê em tenê yekî ji wan wek ya standard bibijêrin. Ew jî divê '''''hevaltî''''' be. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:52, 26 kanûna paşîn 2025 (UTC)
== [[êzidî]] ==
[[êzîdî]]/[[êzdî]] - -[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:04, 11 çiriya paşîn 2020 (UTC)
{{rfk|êzidî|r=158}} û wek kelîmeya jêçêbûyî: "êzidîtî". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:48, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} [[êzdî]]--[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 18:16, 7 adar 2021 (UTC)
== [[potîn]] ==
[[putîn]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:04, 14 çiriya paşîn 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:59, 19 adar 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di pir cihan de wek "potîn" derbas bûye, lê min li ti derê forma "putîn" nedît. Lewma bi min eşkere ye ku halê standard wek "'''potîn'''" e. Û ger forma din bi temamî bêçavkanî be, em wê formê jî jê bibin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:59, 19 adar 2025 (UTC)
== [[diwazdeh]] ==
[[diwazdeh]] / [[dazde]]
{{rfk|duwazdeh|r=142}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:19, 4 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[xwende]] ==
[[xwenda]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:47, 25 çiriya paşîn 2020 (UTC)
{{rfk|xwende|r=524}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:17, 4 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ji bo kelîmeya "'''xwende'''" divê forma standard "xwende" be, ne ku "xwendî". Maneya kesê perwerdegirtî û kesê du-kelîme-xwendî divê ne eynî be. Lewma ez kelîmeya "xwende" pêşnîyar dikim ji bo awayê standard. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:17, 4 çiriya paşîn 2024 (UTC)
== [[pêşniyaz]] ==
[[pêşniyaz]] / [[pêşniyar]] / [[pêşniyaz kirin]] / [[pêşniyar kirin]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:06, 25 çiriya paşîn 2020 (UTC)
{{rfk|pêşnîyaz|r=359}} Kelîmeyên jêçêbûyî ên "pêşnîyaz kirin", "pêşnîyazî", "pêşnîyazkirin" jî wek standard hatine nîşandan. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 00:12, 4 çiriya paşîn 2024 (UTC)
:Dibe ku ev peyv pêşniyaz be ne pêşn(î)yaz. Ev peyv hîn bêtir nêzî hizir e. Lewra niyaz wateya daxwazê dide lê niyar wateya neyartiyê dide. Divê ev peyv pêşniyaz be. [[Bikarhêner:Mûhemmed Seîd|Mûhemmed Seîd]] ([[Gotûbêja bikarhêner:Mûhemmed Seîd|gotûbêj]]) 21:03, 28 tebax 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Balyozxane}} Wê çi ê standardkirin? Yan ti dixwazê ya standard werê guhertin? -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:06, 25 çiriya paşîn 2020 (UTC)
:{{r|Guherto}} Nabînî pêşniyar dibêje pêşniyaz rast e pêşniyaz kirin dibêje pêşniyar kirin rast e. Kîjan rast e? --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:21, 25 çiriya paşîn 2020 (UTC)
::Qusûrê ne mêzêne, mi tenê rûpela pêşniyaz vekir û dît ku dibêjê pêşniyaz / pêşniyar. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 20:56, 25 çiriya paşîn 2020 (UTC)
== [[kamil]] ==
[[kamil]], [[kamîl]], [[kamul]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:02, 29 çiriya paşîn 2020 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:49, 3 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ev peyveke Erebîye, lê dema ku Kurd wê bilêv dikin ″kamil″ dibêjin.Lewma ″kamil″ rasttire, ″kamîl û kamul″ bilêvkirinên tirk û hindîyane. [[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 17:26, 7 adar 2021 (UTC)
{{şîrove}} Ya standard bi eşkereyî "kamil" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:49, 3 çiriya paşîn 2024 (UTC)
: Lê di Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî) - Tirkî ya Nû ya M. Malmîsanij û yên din de cih dane madeya "tekamil" û formên wê yên nestandard ên wek ''tekamîl'', ''tekamul''. Lewma ya standard li gor vê ferhengê dide "'''kamil'''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:54, 3 çiriya paşîn 2024 (UTC)
= Daxwazên standardkirinê: 2021 =
== [[fêrgeh]] ==
[[fêrgeh]] / [[fêrge]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:58, 5 kanûna paşîn 2021 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:36, 3 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} [[fêrgeh]] bê guman rasttire, ev tîpa ″h″ di gellek peyvanda li hin herêman nayê bilêvkirin.Lê ev nayê manê ku ″h″ tunne.
{{şîrove}} Herçend ev kelîme di Ferhenga Kurdî ya Rastnivîsînê ya M. Malmîsanij û yên din de derbas nabe jî, ji madeyên "azmûngeh" (r. 54), "dadgeh" (r. 120), "dangeh" (r. 122) û yên din tê famkirin ku ji bo paşgira "-geh"ê (formên din: "-ge") ya standard "-geh" e. Lewma em karin bêjin ku ji bo vê kelîmeyê divê ya standard bibe "fêrgeh". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:36, 3 çiriya paşîn 2024 (UTC)
== [[bêhêwî]] ==
[[bêhewî]]
Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:52, 23 kanûna paşîn 2021 (UTC)
{{rfk|hewî|r=202}} Ji bo maneya "kuma" awayê standard wekî "hewî" hatiye nîşandan. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:25, 3 îlon 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Peyvên "hewî" û "hêwî" her du jî xweyî çendîn maneyan in. Lewma ger beralîkirin were danîn divê maneya ku jê tê beralîkirin jî were nivîsîn. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:25, 3 îlon 2023 (UTC)
== [[pêşnûma]] ==
[[pêşnima]]
Divê bên standardkirin. Bi îxtîmalekî mezin ya standard "pêşnima" ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:20, 12 sibat 2021 (UTC)
'''Eynî mijar li jêr hatîye vekirin. Ji bo standardkirinê binêrin ya jêr.''' -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:15, 3 çiriya paşîn 2024 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
[[pêşnima]]--[[Bikarhêner:Mihemed Qers|Mihemed Qers]] ([[Gotûbêja bikarhêner:Mihemed Qers|gotûbêj]]) 18:13, 7 adar 2021 (UTC)
== [[jenosîd]] ==
[[jînoside]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:39, 13 sibat 2021 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:13, 3 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} "jînoside" an "jînosid" ? Min nivîsandinên formên "jînosideyê" an "jînosideya" pirr nedîtiye...--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:48, 13 sibat 2021 (UTC)
{{şîrove}} Bi min ya standard "[[jenosîd]]" e. Halên din ên "cenosîd", "jînosîd", "jînosid" kêm-li-kar in. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:13, 3 çiriya paşîn 2024 (UTC)
== [[kesaxtin]] ==
- [[kezaxtin]]
Standardkirin hewce ye. Bi ya min ya rast "kesaxtin" e, yê din xelet e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:57, 14 sibat 2021 (UTC)
{{rfk|kesaxtin|r=250}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:07, 3 çiriya paşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[derazîng]] ==
/ [[derazînk]]
Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:54, 19 sibat 2021 (UTC)
{{rfk|derazînk|r=127}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:53, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Bikarhêner}} bira navê sernavî neguherîne. Parametreya yekem ya şablona {{ş|ds}} divê sernavê vê gotûbêjê be.--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:51, 19 sibat 2021 (UTC)
== [[rovî]] ==
[[rûvî]]/[[rîvî]]. Bi ya min ji bo maneya "guhandarek" rovî zêdetir tê bikaranîn. Bi maneya "hinav" jî du rûpel ([[rûvî]]/[[rîvî]]) hene.--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:19, 19 adar 2021 (UTC)
{{rfk|rûvî|r=423}} Bi maneya "ince bağırsak". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:40, 30 çiriya pêşîn 2024 (UTC)
{{rfk|rovî|r=419}} Bi maneya "tilkî". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:41, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[esman]] ==
[[esman]] | [[asman]] | [[ezman]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:00, 24 adar 2021 (UTC)
{{rfk|esman|r=152}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:38, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ji ber ku ji didu peyvan pêk tê wek [[asê]]+[[man]]. Ezê peyva [[asman]] pêşniyar bikim. [[Bikarhêner:Key Mîrza|Key Mîrza]] ([[Gotûbêja bikarhêner:Key Mîrza|gotûbêj]]) 16:30, 10 gulan 2021 (UTC)
== [[niha]] ==
[[niha]] / [[niho]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 21:22, 25 adar 2021 (UTC)
{{rfk|niha|r=333}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:35, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[sûper]] ==
Standardkirinê teleb dikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:55, 26 adar 2021 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:27, 3 îlon 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Li gorî telefûzê ya standard "super" e, kes halê dirêjkirî nabêje, bi qasî zanîna min. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:48, 30 çiriya pêşîn 2024 (UTC)
== [[sûperlatîv]] ==
Standardkirinê teleb dikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:57, 26 adar 2021 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:28, 3 îlon 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi ya min ya rast "super" û "superlatîv" e, ji ber ku gava em bi lêv dikin em wek "superlatîv" bi lêv dikin, ne wek "sûperlatîv". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:57, 26 adar 2021 (UTC)
== [[radîkalîzm]] ==
/[[radikalîzm]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 16:21, 30 nîsan 2021 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:46, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Balyozxane}} Bi min peyveke wekî "radikalîzm" tine ye û bi xeletî wisa hatiye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:16, 17 tebax 2022 (UTC)
== [[texdîr]] ==
[[teqdîr]] standardkirin hewce ye. Bi min "teqdîr" standardtir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:52, 18 gulan 2021 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:45, 30 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Pir eşkere ye ku ya standard "'''teqdîr'''" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:45, 30 çiriya pêşîn 2024 (UTC)
== [[tayîn]] ==
[[teyîn]] / [[tayîn]] / [[teîn]] / [[tayin]]. Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:30, 9 tîrmeh 2021 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:55, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Mirov di navbera "tayîn" û "tayin" de bêqerar dimîne. Lê bi min divê wek halê standard "'''tayîn'''" were dîyarkirin. Ji ber ku zêdetir li kar e (di lêgera Google Searche de jî zêdetir "tayîn" derdikeve, ''formata lêgerînê: wek "tayîna xwe" / "tayina xwe"''). -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:55, 29 çiriya pêşîn 2024 (UTC)
== [[nod]] ==
* [[not]]
Ev made li vir heye: [[Wîkîferheng:Daxwazên_standardkirinê#not,_nod]] Bi şaşî tekraren hatîye tevlîkirin. Ji bo agahîyan lê binêrin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:00, 29 çiriya pêşîn 2024 (UTC)
'''Gotûbêj:''' <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Zanavano}}. Bnr. [[WF:Rêzikên standardkirinê]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:28, 1 çiriya pêşîn 2021 (UTC)
= Daxwazên standardkirinê: 2022 =
== [[xizar]] ==
[[xizar]], [[xîzar]]. Standardkirin hewce ye.{{bêîmze|Bikarhêner}}
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:46, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Forma xizar hema bêje li ti derê tine ye. Lewma halê standard divê "xîzar" be. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:46, 29 çiriya pêşîn 2024 (UTC)
== [[Nû Zelenda]] ==
[[Zelandaya Nû]] zêdetir tê bikaranîn.[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:18, 9 kanûna pêşîn 2022 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:39, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di '''Rêbera Rastnivîsînê''' ya Komxebata Kurmancîyê de (r. 37) wek "Zelandaya Nû" hatîye qeydkirin. Di kitêba '''Rastnuştişê''' ya Gruba Xebatê ya Vateyî de jî ev peyv wek "Zelandaya Newîye" (r. 67) hatîye qeydkirin. Lewma bi min divê halê standard bibe "Zelandaya Nû." -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:39, 29 çiriya pêşîn 2024 (UTC)
= Daxwazên standardkirinê: 2023 =
== [[pirzik]] ==
[[pizik]] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:37, 15 kanûna paşîn 2023 (UTC)
* {{rfk|pizrik}} --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 23:37, 15 kanûna paşîn 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min forma wek "{{g|ku|pizrik}}" jî heye û divê ew jî were standardkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:53, 31 tebax 2023 (UTC)
== [[bûro]] ==
{{g|ku|bûro}} / {{g|ku|buro}} / {{g|ku|buriyo}} standardkirin hewce ye. --- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:06, 22 kanûna paşîn 2023 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:58, 31 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di ''Rêbera Rastnivîsînê'' ya Komxebata Kurmancî de di rûpela 139em de nimûneyeke wek "'''buroya wî'''" hatiye dayîn. Ji vê nimûneyê mirov dikare bêje ku ji bo vê peyvê awayê standard wekî "buro" dibînin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:53, 3 îlon 2023 (UTC)
== [[fezîlet]] ==
[[fazîlet]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 11:47, 26 kanûna paşîn 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ev peyv jî ji erebî tê. Herçiqas di hin peyvên reh-erebî de (wek nimûne "edalet") ji bo eyna erebî "e" hatibe tercîhkirin jî bi min ji bo em li vir nêzî telafûza di pratîkê de bibin, forma bi "e"yê neyê tercîhkirin jî dibe. Lewma ez "fazîlet"ê pêşnîyar dikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:57, 29 çiriya pêşîn 2024 (UTC)
== [[Uman]] ==
{{g|ku|Uman}}, {{g|ku|Oman}}, {{g|ku|Iman}}, {{g|ku|'Iman}}, {{g|ku|'Uman}}, {{g|ku|Umman}}
Standardkirin hewce ye.
{{rfk|}}
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di '''''Rêbera Rastnivîsînê''''' ya Komxebata Kurmancîyê de awayê "'''Uman'''" wekî standard hatiye nîşandan. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:34, 3 nîsan 2023 (UTC)
== [[hejî]] ==
{{g|ku|hejî}}, {{g|ku|hêja}}
Standardkirin hewce ye.
{{rfk|}}
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} {{r|Ferhengvan}} Bi min ev herdu, wekî formên eynî peyvê dixuyin. Lewma standardkirinê teleb dikim. Her çiqas berçav be ku awayê stadard "hêja" ye jî, li vir teleb dikim. Lê ger ne formên eynî peyvê bin, xêra xwe li vir binivîsin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 01:52, 7 nîsan 2023 (UTC)
::{{Silav|Bikarhêner}} Rast dibêjî, "hejî" formeke devokî (behdînî) ya peyva "hêja" ye. --[[Bikarhêner:Ferhengvan|Ferhengvan]] ([[Gotûbêja bikarhêner:Ferhengvan|gotûbêj]]) 20:31, 7 nîsan 2023 (UTC)
== [[sererast]] ==
{{q|ku|sererast}}, {{q|ku|serrast}} Standardkirin hewce ye. Bi min xuya ye ku forma standard "sererast" e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:02, 25 nîsan 2023 (UTC)
{{rfk|serrast, serrast kirin|r=437}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:41, 31 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bila piştî gihîştina netîceyê, standardkirina formên din ên wekî "sererastkirin" / "serrastkirin", "sererast kirin" / "serrast kirin" jî neyê jibîrkirin. Bi min divê forma standard bibe "sererast". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:41, 31 tebax 2023 (UTC)
== [[fiqih]] ==
{{g|ku|fiqh}} / {{g|ku|fiqih}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:16, 30 nîsan 2023 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:16, 30 nîsan 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di ''Rêbera Rastnivîsînê'' ya Komxebata Kurmanciyê de (r.52, 53) behsa vê mijarê kirine: Bi sernavê "'''Nivîsîna hin bêjeyên bîyanî yên yekkîteyî ku di kurmancîyê da dibin dukîteyî'''", wek nimûne jî "'''aqil ✓''', '''aql ✗'''", "fikir ✓, fikr ✗", "qebir ✓, qebr ✗", "qebiz ✓, qebz ✗", "qedir ✓, qedr ✗", "qehir ✓, qehr ✗"...
Li binî jÎ dibêjin divê li gel îzafeyê [i] bê parastin, wekî "aqilê min ✓, aqlê min ✗", "fikira min ✓, fikra min ✗", "qebira min ✓, qebra min ✗"...
Lewma bi dîtina min, li gor vê rêberê divê wekî "fiqih" bibe ya standard. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:38, 31 tebax 2023 (UTC)
== [[îqaz]] ==
{{g|ku|îqaz}} - {{g|ku|îkaz}}. Standardkirin hewce ye. Piştî qerardana li ser forma standard, divê peyvên girêdayî wan jî yên wek "îqazker", "îqaz kirin" hwd jî bên guherandin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:14, 19 gulan 2023 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:14, 19 gulan 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di ''Rêbera Rastnivîsînê'' ya Komxebata Kurmanciyê de (r. 80) dibêjin "'''[q]ya bêjeyên biyanî neke [k]'''". "k"ya vê peyvê jî bi eslê xwe "[q]" ye, û lewma divê awayê standard bibe "îqaz". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:22, 31 tebax 2023 (UTC)
== [[hundirîn]] ==
{{g|ku|hindirîn}}, {{g|ku|hundirîn}} standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 17:41, 27 gulan 2023 (UTC)
{{rfk|hundir|r=217}} Ji ber ku awayê "hundir" wek standard hatiye bijartin, li gor vê "hundirîn" jî dibe awayê standard. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:40, 29 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[wasita]] ==
{{g|ku|wasita}}, {{g|ku|wesite}}, {{g|ku|wasîte}}, {{g|ku|wasta}}, {{g|ku|wesita}}, {{g|en|wesita}}, {{g|en|wesîte}}, {{g|en|wasite}}
standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:06, 21 hezîran 2023 (UTC)
{{rfk|wasîte|r=504}}
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[qebe]] ==
{{g|ku|qebe}} / {{g|ku|qeba}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:10, 27 tebax 2023 (UTC)
{{rfk|}} --[[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:35, 29 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min forma "qeba" baştir e ji bo halê standard. Helbet gava dîyarkirina halê standand divê em halên emilandinê û peyvên jêçêbûyî jî bifikirin. Ji bo vê peyvê wek "qebetî" / "qebatî". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:26, 29 çiriya pêşîn 2024 (UTC)
== [[sewgûr]] ==
{{g|ku|sewgur}} / {{g|en|sewgûr}} / {{g|en|sewgir}} / {{g|en|sewgor}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:33, 29 tebax 2023 (UTC)
{{rfk|sewgurî, sewgura sibê|r=439}} Di ferhengê de wek "sewgurî" û wek "sewgura sibê" hatiye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 21:33, 29 tebax 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[Danmarka]] ==
[[Danîmarka]] zêdetir tê bikaranîn--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 19:54, 3 îlon 2023 (UTC)
{{rfk|}} Di vê ferhengê de peyvên "danîmarkî" û "danîmarqî" ber bi rûpela "danmarkî" ve yê hatine alîkirin. Lewma mirov fam dike ku navê vî welatî li gorî vê ferhengê "'''Danmarka'''" ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:12, 3 îlon 2023 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] Bi min ew mantiqeke şaş e. Loma em li ferhengên din jî binêrin baştir be. Ez fêm nakim çawa ferhengeke rastnivîsînê navên welatan qeyd nekirine. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:27, 3 îlon 2023 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi ya ''Rêbera Rastnivîsînê'' ya Komxebata Kurmancî jî awayê standard "'''Danîmarka'''" ye (r. 31). Ez bixwe jî ji bo awayê standard "Danîmarka"yê pêşniyar dikim. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 20:55, 3 îlon 2023 (UTC)
== [[dijmîn]] ==
{{g|ku|dijmîn}}, {{g|ku|dijûn}} Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:57, 12 îlon 2023 (UTC)
{{rfk|dijûn|r=135}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:04, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[fîlmografî]] ==
[[filmografî]]
:--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:38, 20 îlon 2023 (UTC)
{{rfk|fîlm|r=166}} Şiklê standard ji bo peyva "film / fîlm / fîlim" wek "fîlm" hatîye nîşandan. Lewma li gor vê awayê standard dibe "'''fîlmografî'''". -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:07, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[not]], [[nod]] ==
[[not]], [[nod]]--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 20:23, 11 çiriya pêşîn 2023 (UTC)
{{rfk|nod|r=338}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:09, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[afrîkî]] ==
[[afrîkî]] / [[afrîkayî]] ?--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:06, 9 çiriya paşîn 2023 (UTC)
{{rfk|afrîkayî|r=41}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:11, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[eware]] ==
[[eware]]/[[aware]]
:Bi maneya ''avare/aylak/serseri''
{{rfk|153}}
:Ji bo maneya ''penaber'' di vê ferhengê de standardek tine ye. Loma divê du rûpel hebe ji bo kelîmeya kurmancî ya eware û yekî din ji bo maneya penaber.
--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 12:14, 8 sibat 2024 (UTC)
=== Gotûbêj ===
<!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Di ferhenga ''Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû'' de jixwe standarda ji bo vê peyvê heye. Ji bo maneya penaber jî hebûna vê maneyê bi min bişibhe ye, wekî din ''ger hebe jî'' ji ber ku ji berhirefkirina maneya "''yê derbeder û bê cih û mal''" hatîye çêkirin, dîsa girêdayî wê maneya eslî ya derbiderîyê ye, lewma divê li ser rûpelekî werin standardkirin û ew rûpel jî pêşnîyara di kitêba nav-zikirkirî de be baştir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:22, 18 hezîran 2024 (UTC)
== [[wesetî]] ==
[[wasatî]] / [[wesetî]] standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 10:48, 29 adar 2024 (UTC)
{{rfk|wesetî|r=506}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:27, 18 hezîran 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[se direwît, karwan bi rêka xwe diçît]] ==
{{g|ku|se direwît, karwan bi rêka xwe diçît}} / {{g|ku|se direwin, karwan diborin}} / {{g|ku|seh direye kerwan dibihore}} Divê ev her sê form werin standardkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:51, 16 hezîran 2024 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:26, 7 tîrmeh 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min ya standardtir "'''seg direye karwan dibihure'''" ye ("seg", ''Ji Bo Rastnivîsînê Ferhenga Kurdî (Kurmancî) - Tirkî ya Nû'', r. 429; "reyîn", r. 410; "karwan", r. 240; "bihurîn", r. 83). Wekî form (di qismê "Herwiha" de) em "'''bi ewtîna seyan, ji rê namîne karwan'''" jî nîşan bidin. Lê ji alîyê xwedîçavkanîbûnê ve jî divê werin kontrolkirin. Min ji bo "''bi ewtîna seyan, ji rê namîne karwan''" çavkanî peyda nekir, min ji bo "''seg direye karwan dibihure''" jî tenê çavkanîyekê peyda kir ([https://www.tirejafrin.com/pdf/Pend%20.pdf ''Pend û Peng''], Bîlal Hesen, di kitêbê de wek "seh direye kerwan dibihore" derbas dibe). -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 08:26, 7 tîrmeh 2024 (UTC)
== [[serobino bûn]] ==
{{g|ku|serobino bûn}} / {{g|ku|serûbin bûn}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 16:21, 14 tebax 2024 (UTC)
{{rfk|serûbin|r=438}} "serûbin bûn, serûbin kirin, serûbinbûn, serûbinkirin, serûbinkirî" wek peyvên jêçêbûyî hatine nîşandayîn. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 12:19, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[sanî]] ==
[[sanî]] / [[saniye]]
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:45, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Ez dibêjim ber saniye be. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 15:22, 1 îlon 2024 (UTC)
{{şîrove}} Ez dibêjim bila "sanîye" be. Em wek sanîye telafûz dikin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:45, 29 çiriya pêşîn 2024 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] "Sanîye" çênabe. Li vir em "îy" nanivîsînin. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 13:34, 10 çiriya paşîn 2024 (UTC)
== [[pêşnûma]] ==
[[pêşnûma]] / [[pêşnima]] Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:00, 29 çiriya pêşîn 2024 (UTC)
{{rfk|}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:00, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi min divê "pêşnima" be. Formên bi "u"yê zahfê caran nayên emilandin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:00, 29 çiriya pêşîn 2024 (UTC)
== [[payîz]] ==
[[payîz]] / [[payiz]]
Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:12, 29 çiriya pêşîn 2024 (UTC)
{{rfk|payîz|347}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:12, 29 çiriya pêşîn 2024 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[behîv]] ==
{{g|ku|behîv}} / {{g|ku|bahîv}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 11:41, 26 kanûna paşîn 2025 (UTC)
{{rfk|behîv|63}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:38, 26 kanûna paşîn 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[mitîng]] ==
[[mitîng]] / [[mîtîng]] Standardkirin hewce ye. -- --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 21:47, 27 kanûna paşîn 2025 (UTC)
{{rfk|mîtîng|r=316}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:43, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
== [[rûtîn]] ==
[[rûtîn]], [[rutîn]] Standardkirin hewce ye. — [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:47, 18 sibat 2025 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:42, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şirove}} -->
{{şirove}} Bi min eşkere ye ku ya standard "'''rutîn'''" e. Peyvên telafûza wan dirêj be, bi "û"yê tên gotin, lê li vir dirêj nayê gotin. Lewma bi "u"ya bêkumik çêtir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:42, 1 hezîran 2025 (UTC)
== [[parleman]] ==
[[parleman]] / [[perleman]] / [[parlament]]/[[parlemento]] / [[perlemen]], Standardkirin hewce ye.--[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 00:53, 22 sibat 2025 (UTC)
{{rfk|parlamento|r=345}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:39, 1 hezîran 2025 (UTC)
=== Gotûbêj ===
{{şîrove}} Di ferhenga "Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû" da bi maneya ''meclis''ê wek "parlamento" hatîye qeydkirin, bi maneya ''azayê meclisê'' wek "parlamenter" hatîye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:39, 1 hezîran 2025 (UTC)
== [[sond]] ==
[[sond]], [[sûnd]]—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:41, 21 adar 2025 (UTC)
{{rfk|sond|r=447}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:35, 1 hezîran 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şirove}} -->
{{şirove}}
== [[sifet]] / [[sîfet]] ==
{{rfk|}}--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:41, 1 gulan 2025 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Bi dîtina min wek "sifet" zêdetir li kar e. (Ger rast transkrîbe kiribin) Di eserên berê yên kurdî de wek "sîfet" hatine gotin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:29, 31 gulan 2025 (UTC)
== [[mane]] ==
[[mane]], [[mana]]—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:13, 30 gulan 2025 (UTC)
{{rfk|mane|r=297}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:20, 31 gulan 2025 (UTC)
=== Gotûbêj ===
{{şirove}} {{r|Bikarhêner}} [https://ferheng.wmezopotamyaye.org/ weqfa mezopotamyaye] dibêje forma standard "mana" ye. Dikarî ji ferhenga rastnivîsînê binêrî? --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:18, 30 gulan 2025 (UTC)
: {{r|Wikihez}} Di Ferhenga Rastnivîsînê de wek "mane" (jê "mane dan", "manedan", "manedar", "manedarî") hatîye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 06:20, 31 gulan 2025 (UTC)
== [[memnû]] ==
{{g|ku|memnû}} {{g|ku|memnûh}} Standardkirin lazim e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:34, 1 hezîran 2025 (UTC)
{{rfk||}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:34, 1 hezîran 2025 (UTC)
=== Gotûbêj=== <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}} Eşkere ye ku ya standard "'''memnû'''" ye. Forma "memnûh" ne li kar e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:34, 1 hezîran 2025 (UTC)
== [[niha]] ==
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{rfk|niha|r=333}} [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 11:22, 16 sibat 2026 (UTC)
== [[gîya]] ==
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{rfk|gîya|r=180}} [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 11:24, 16 sibat 2026 (UTC)
Kelîmeya jêçêbûyî: {{g|ku|gîyagezk}} (mê, mane "''ısırgan otu''") -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:11, 5 tîrmeh 2026 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]], @[[Bikarhêner:Rewiye tine|Rewiye tine]] Bi ya min, ev ne meseleya standardkirinê ye lê ya îmlayê ye çûnkî ev problema îy - iy zêde tevlihev e. Dengdanek hewce ye ku bibêje konsensusek heye li ser bikaranîna -îy û ne -iy. Lê dengdanek wisa dê têk biçe ji ber ku hejmara kesên -îy tercih dikin kêmtir e li ser Wikiferhengê. Loma divê ev gotûbêj wek çênebû were tayînkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:04, 5 tîrmeh 2026 (UTC)
::Bi qenaeta min jî "'''îy'''" rasttir e. Divê em di demên pêş da wî meseleyê hel bikin. Feqet wekî ku te jî got, ji ber ku vê gavê li ser vê qerar nehatîye dayîn, em karin wek xwe bihêlin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 07:45, 6 tîrmeh 2026 (UTC)
== [[hincet]] ==
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{rfk|hincet|r=210}} [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 11:18, 16 sibat 2026 (UTC)
== [[kurmî]] ==
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
{{rfk|kurm (navdêr), kurmî (rengdêr)|r=269}} [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 08:55, 23 adar 2026 (UTC)
:kurmî berbelav e hem jî standardîze bûye bi min. Hem em dibêjin kurm bi gelemperî kirm kêm e. [[Bikarhêner:Bibliophile|Bibliophile]] ([[Gotûbêja bikarhêner:Bibliophile|gotûbêj]]) 10:55, 23 adar 2026 (UTC)
::Lê balkêş e ku rehê xwe yê qedîm zêdetir wekî [[kirm]] xuya dike. Lê etîmolojî nikare bikeve pêşberî bikaranîna giştî peyvekê ji aliyê xelkê ve. Helbet ev fikra min a şexsî ye. Ji bo selameta ferhengê kurdî ev yek gerek e bête çareserkirin. [[Bikarhêner:Bibliophile|Bibliophile]] ([[Gotûbêja bikarhêner:Bibliophile|gotûbêj]]) 11:01, 23 adar 2026 (UTC)
== [[tihn]] ==
[[tihn]], [[tîn]], [[tên]]
{{rfk|tîn (mê, mane: ''ısı; hararet, sıcaklık'')|r=488}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:18, 5 tîrmeh 2026 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
Ez bawer dikim ku ''tîn'' zêdetir tê bikaranîn.
== [[nehwirandin]] ==
[[nehwirandin]], [[norandin]]
bnr [[Gotûbêj:norandin]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:52, 3 tîrmeh 2026 (UTC)
{{rfk|nehwirandin|r=328}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 19:05, 5 tîrmeh 2026 (UTC)
=== Gotûbêj ===
== [[rus]], [[rûsî]] ==
[[rus]], [[rûs]], [[rûsî]], [[rusî]]
bnr. [[Gotûbêja kategoriyê:Rusî]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 07:11, 5 tîrmeh 2026 (UTC)
{{rfk|rûs, rûsî|r=422}} "rûs" wek navê miletê, "rûsî" wek navê zimanê hatîye qeydkirin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:57, 5 tîrmeh 2026 (UTC)
=== Gotûbêj ===
== [[dûkel]] ==
{{g|ku|dûkel}} / {{g|ku|dûkêl}} Standardkirin hewce ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:14, 19 tîrmeh 2026 (UTC)
{{rfk|dûkel (nêr)|r=143}} -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 23:16, 19 tîrmeh 2026 (UTC)
=== Gotûbêj === <!--Li jêr fikrên xwe binivîsin. {{şîrove}} -->
{{şîrove}}
gt18wt015vg3ywuc44xdoohhmsq5y0i
Gotûbêja bikarhêner:Avestaboy
3
864603
6534907
6534884
2026-09-20T14:22:20Z
Avestaboy
5833
/* jenk */ Bersiv
6534907
wikitext
text/x-wiki
{{xêrhatin}}
== Xêrhatin ==
Merheba bikarhênerê me yê hêja: ''Avestaboy'',
Bi xêr hatî ferhenga kurdî-kurdî WÎKÎFERHENGê {{=)}}
Em hêvîdar in ku tu jî di avakirina vê ferhengê de alîkariya me bikî.
Eger pirsên te hebin, xêra xwe ji me re binivîse.
Xebatxweş.
:::::: -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 05:37, 20 tebax 2020 (UTC)
== [[jenk]] ==
Merheba @[[Bikarhêner:Avestaboy|Avestaboy]], di madeya [[jenk]] de 3 çavkanî hewce ne. Ji xeynî Farqînî ti çavkanî nîne. Bnr. [[Wîkîferheng:Pîvekên tevlîkirinê|Krîterên tevlîkirinê]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:29, 19 îlon 2026 (UTC)
:Silav @[[Bikarhêner:Wikihez|Wikihez]] Weke ku tu baş dizanî, ti ferhengeke ku hemû peyvên zimanê kurdî di nav xwe de bigire nîne. Wîkîferheng tam jî dixwaze vê yekê biguherîne. Tenê ji ber ku peyvek di ferhengên din de cih nagire, ev nayê wê wateyê ku ew şaş e. Wekî din, peyva '''jenk''' di ferhenga înternetê ya [[Enstîtuya Kurdî ya Stenbolê]] ferhengkurdi.orgê de jî heye.
:https://ferhengkurdi.org/tr/kelime/jenk-m [[Bikarhêner:Avestaboy|Avestaboy]] ([[Gotûbêja bikarhêner:Avestaboy#top|gotûbêj]]) 08:16, 20 îlon 2026 (UTC)
::Ev lînk jixwe ferhenga zana farqînî ye, tê de û hemû ferhengên kurdî de jî gelek xeletî hene, loma şerta hebûna 3 çavkaniyan elzem e. Li cem min bi hezeran pdf û 60+ ferhen bi pdfê hene, tenê ya farqînî de ev kelîme derbas dibe. Loma eger 3 çavkaniyên din nayên dîtin, ew ê were jêbirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:53, 20 îlon 2026 (UTC)
:::Silav @[[Bikarhêner:MikaelF|MikaelF]], @[[Bikarhêner:Ghybu|Ghybu]], @[[Bikarhêner:Ferhengvan|Ferhengvan]] helwesta we li ser vê çawa ye ? [[Bikarhêner:Avestaboy|Avestaboy]] ([[Gotûbêja bikarhêner:Avestaboy#top|gotûbêj]]) 14:22, 20 îlon 2026 (UTC)
001fhij3nt1u2o0fmfc30wmr7v1lwah
Nimînok:Ferhengok
102
874342
6534895
6532060
2026-09-20T13:37:47Z
Wikihez
7344
/* D */
6534895
wikitext
text/x-wiki
Ferhengokeke terimên di Wîkîferhengê de tên bikaranîn. Ji bo lînkkirina vê rûpelê şablona {{ş|ferhengok}} bi kar bînin.
<div style="float:left;">{{TOC}}</div>
{{bend}}
== A ==
; {{lenger|akronîm}}[[akronîm]], ''[[:en:Appendix:Glossary#acronym|acronym]]''
: Kurtenavê ku ji herfên pêşîn ên çend kelîmeyan çêdibe û wek kelîmeyeke serbixwe tê xwendin. Nimûne ''NATO'', ''UNESCO''. Bide ber: [[#înîtîalîzm]]
; {{lenger|artîkel}}[[artîkel]]{{nobold|, [[veqetandek]], [[zêder]]}}
; {{lenger|analîza şiklî}}[[analîz]]a [[şikl]]î
:Etîmolojiya termekê ye ku li ser temela parçeyên wê yên nasbar ên di şiklê nûjen ê ziman de cih digirin hatiye avakirin; bêyî ku guherîna deng li ber çavan bê girtin, yanî pêkhateya wê ya senkronîk:
:: {{qertaf|en|earth|-en|nocat=1}} ji bo {{q|en|earthen}} (mîrasmayî ji {{navê zimanî|enm}} ji {{navê zimanî|ang}})
:: {{qertaf|en|bio-|-logy|nocat=1}} ji bo {{q|en|biology}} (deyn ji Latîniya nû)
:: {{qertaf|en|circle|-ar|nocat=1}} ji bo {{q|en|circular}} (mîrasmayî ji {{navê zimanî|enm}} ji {{navê zimanî|fro}} û latîniya dereng), bi guherîna {{IPAchar|/əl/}} ber bi {{IPAchar|/jəl/}}
:Analîzên şiklî behsê vê yekê dike ku mirovên ku tarîxa kelîmeyekê nizanin, li xuyanga kelîmeyê ya îro dinêrin û wê li gorî parçeyên ku nas dikin tehlîl û ava dikin.
== B ==
; {{lenger|baneşan}}[[baneşan]], [[#nîda]]
; {{lenger|belavbûn ji zimanekî din}}[[belavbûn]] ji zimanekî din, ''[[:en:Appendix:Glossary#contamination|contamination]]''
: Guherîna şikl an maneya kelîmeyekê di bin tesîra kelîmeyeke din de. Nimûne bi îngilîzî {{q|en|female}} ji fransiya kevn ''femelle'' e, lê şiklê wê di bin tesîra {{q|en|male}} de guheriye.
; {{lenger|binavkirî}}[[binavkirî]]
: Bi-nav-kirî. Formeke navdêrê ye. '''Kevirê''' min. '''Sêva''' te.
; {{lenger|biwêj}}[[biwêj]], [[#tebîr]]
; {{lenger|bireser}}[[bireser]], [[#mefûl]]
: Pêkhateya ku kar lê tê kirin. Nimûne ''Xecê sêv xwar.'' Kelîmeya ''sêv'' di vê cimleyê de bireser e. Bide ber: [[#kirde]]
== C ==
; {{lenger|cînav}}[[cînav]], [[#zemîr]]
; {{lenger|cotreh}}[[cot]][[reh]]
:Kelîmeyên ku ji eynî rehî çêbûne lê belê bi riyên cuda ketine zimanekî. Bi {{navê zimanî|en}} ''[[:en:Appendix:Glossary#doublet|doublet]]''. Bide ber [[#hevreh]]
== Ç ==
== D ==
; {{lenger|daçek}}[[daçek]], [[#edat]]
; {{lenger|dariştî}}[[dariştin]], ''dariştî''
:Çêkirina kelîmeyan bi [[#pêşgir|pêşgir]], [[#navgir|navgir]] û [[#paşgir|paşgir]]an. Herwiha di etîmolojiyê de behsa wergirtinên kelîmeyan ji zimanên din dike ku ne diyar e rasterast hatiye deynkirin yan jî mîras maye.
; {{lenger|deynkirina neadaptekirî}}[[deynkirin]]a neadaptekirî, ''[[:en:Appendix:Glossary#unadapted borrowing|unadapted borrowing]]''
: Kelîmeya deynkirî ya ku bi şiklê zimanê jê hatiye girtin dimîne û li rêzik û bilêvkirina zimanê wergirtî nayê adaptekirin. Nimûne {{q|ku|laptop}}.
; {{lenger|deynkirina ortografîk}}[[deynkirin]]a ortografîk, ''[[:en:Appendix:Glossary#orthographic borrowing|orthographic borrowing]]''
: Kelîmeya ku bi şiklê xwe yê nivîsandinê ji zimanekî din tê deynkirin, lê li gorî herfên wê tê xwendin, ne li gorî bilêvkirina zimanê ku jê hatiye girtin.
; {{lenger|dijmane}}[[dijmane]], [[#dijwate]]
: Kelîmeyên ku maneyan wan zidî hev in yanî %100 cuda ye. Nimûne [[mezin]] û [[biçûk]] dijmaneyên hev in.
; {{lenger|dijwate}}[[dijwate]], [[#dijmane]]
: Kelîmeyên ku maneyan wan %100 cuda ye. Nimûne [[mezin]] û [[biçûk]] dijwateyên hev in.
== E ==
; {{lenger|edat}}[[edat]], [[#daçek]]
; {{lenger|elîpsîz}}[[elîpsîz]], ''[[:en:Appendix:Glossary#ellipsis|ellipsis]]''
: Avêtina kelîmeyekê ji tebîrekê, ku mana tebîra temam li kelîmeya mayî bar dibe. Nimûne {{q|en|private}} ji ''private soldier''.
; {{lenger|emilandinên_xelet}}[[emilandina]] [[xelet]], '''emilandinên xelet'''
Kelîmeyên ku ji aliyê çavkaniyên preskrîptîv ve xelet tên qebûlkirin û dibêjin divê neyên emilandin. Bide ber {{navê zimanî|en}} ''[[:en:Appendix:Glossary#proscribed|proscribed]]''.
== Ê ==
== F ==
; {{lenger|fail}}[[fail]], [[#kirde]]
: Unsira ku di cimleyekê de karî dike. Nimûne ''Xecê sêv xwar.'' Kelîmeya ''Xecê'' di vê cimleyê de fail e. Bide ber: [[#mefûl]]
; {{lenger|fiîl}}[[fiîl]], [[#lêker]]
: Kelîmeyên ku nîşanê kirinê an bûyînê dikin. Nimûne: ''Min sêv xwar.'' Li vir "xwarin" fiîl e ji ber ku karê xwarinê îfade dike.
== G ==
; {{lenger|girêdek}}[[girêdek]]
; {{lenger|gerguhêz}}{{lenger|gh}}[[gerguhêz]], ''gh''
: [[#Fiîl|Fiîl]]a ku [[#mefûl|mefûlê]] werdigire. Nimûne: ''Min sêv xwar.'' Li vir "xwarin" gerguhêz e ji ber ku dikare bi "sêv" were bikaranîn.
== H ==
; {{lenger|hevmane}}[[hevmane]], [[#hevwate]]
: Kelîmeyên ku maneya wan eynî ne. Nimûne {{q|ku|ciwan}} û {{q|ku|xweşik}}
; {{lenger|hevdeng}}[[hevdeng]]
: Kelîmeyên ku wekî kelîmeyeke din tên bilêvkirin lê nivîsîn, mane an jî rehê wan ji hev cuda ne.
; {{lenger|hevreh}}[[hevreh]]
:Kelîmeyên ku ji heman kelîmeyê çêbûne.
; {{lenger|hevwate}}[[hevwate]], [[#hevmane]]
: Kelîmeyên ku wateya wan eynî ne. Nimûne {{q|ku|ciwan}} û {{q|ku|xweşik}}
; {{lenger|hoker}}[[hoker]], [[#zerf]]
== I ==
== Î ==
; {{lenger|înîtîalîzm}}[[înîtîalîzm]], ''[[:en:Appendix:Glossary#initialism|initialism]]''
: Kurtenavê ku herfên wê yek bi yek tên xwendin. Nimûne ''BBC''. Bide ber: [[#akronîm]]
== J ==
== K ==
; {{lenger|kirde}}[[kirde]], [[#fail]]
: Unsira ku di cimleyekê de karî dike. Nimûne ''Xecê sêv xwar.'' Kelîmeya ''Xecê'' di vê cimleyê de kirde ye. Bide ber: [[#bireser]]
; {{lenger|keysa herfê}} [[keys]]a [[herf]]ê
: Halê [[hûrdek]] an [[girdek]] ya peyvê.
== L ==
; {{lenger|lêker}}[[lêker]], [[#fiîl]]
: Kelîmeyên ku nîşanê kirinê an bûyînê dikin. Nimûne: ''Min sêv xwar.'' Li vir "xwarin" lêker e ji ber ku karê xwarinê îfade dike.
== M ==
; {{lenger|mefûl}}[[mefûl]], [[#bireser]]
: Unsira ku kar lê tê kirin. Nimûne ''Xecê sêv xwar.'' Kelîmeya ''sêv'' di vê cimleyê de mefûl e. Bide ber: [[#fail|fail]]
; {{lenger|metatez}}[[metatez]], ''[[:en:Appendix:Glossary#metathesis|metathesis]]''
: Guherîna cihê dengên nav kelîmeyekê. Nimûne di kurmancî de ''berf'' û ''befr''.
; {{lenger|mê}}{{lenger|m}}[[mê]], ''m''
: [[#navdêr|Navdêrên]] ku [[#zayend|zayenda]] wan li gorî mê ditewe. Herwiha bnr. [[#mn|mn]], [[#nêr|nêr]].
; {{lenger|mê û nêr}}{{lenger|mn}}mê û nêr, ''mn''
: [[#navdêr|Navdêrên]] ku [[#zayend|zayenda]] wan li gorî hem mê hem nêr ditewe. Herwiha bnr. [[#mê|mê]], [[#nêr|nêr]].
== N ==
; {{lenger|navdêr}}[[navdêr]]
: Navên tişt (''sêv, heywan, qedeh'') yan mefhûman (''dilşadî, bextiyarî'' hwd.) di rêzimanê de.
; {{lenger|navgir}}[[navgir]]
; {{lenger|navmiletî}}[[navmiletî]]
: Kelîmeyeke deynkirî ya ku di çend zimanan de bi mane û etîmolojiyeke eynî an jî nêzîkî hev derbas dibe, bi gelemperî ji ber peydabûna çend deynkirinên hevdem û/an zincîreyeke deynkirinên li pey hev ku zû diqewimin çêdibin. Di netîceyê de dibe ku peydakirina eslê rast a etîmolojîk a terimekê zimanekî zor an ne mimkûn be. Wek nimûne doktor, otêl, înternet, taksî, an televîzyon.
; {{lenger|nebinavkirî}}[[nebinavkirî]]
; {{lenger|negerguhêz}}{{lenger|ngh}}[[negerguhêz]], ''ngh''
: [[#lêker|Lêker]]a ku [[#bireser|bireserê]] wernagire. Nimûne: "Ez diçim mektebê" li vir çûn negerguhêz e.
; {{lenger|neqiyasbar}}[[neqiyasbar]]
: [[#rengdêr|Rengdêrên]] ku formên wan yên bi ''-tir'', ''-tirîn'' an ''herî'' tune.
; {{lenger|n}}{{lenger|nêr}}[[nêr]], ''n''
: [[#navdêr|Navdêrên]] ku [[#zayend|zayenda]] wan li gorî nêr ditewe. Herwiha bnr. [[#mê|mê]], [[#mn|mn]].
; {{lenger|nêtar}}{{lenger|nt}}[[nêtar]], ''nt''
: [[#navdêr|Navdêrên]] ku [[#zayend|zayenda]] wan ne [[#nêr|nêr]] û ne jî [[#mê|mê]] ye.
; {{lenger|nêzîkî}}[[nêzîkî]] ~
: Îşareta ~ (tîlde), wextê tê pêşiya salekê, tê maneyê ku ev sal tam ne belî ye.
; {{lenger|nîda}}[[nîda]] [[#baneşan|baneşan]]
; {{lenger|nomînalîzasyon}}[[nomînalîzasyon]], ''[[:en:Appendix:Glossary#nominalization|nominalization]]''
: Çêkirina îsmekê ji kelîmeyeke cureyeke din, bi taybetî ji fiîlekê an sifetekê. Nimûne ''[[xwendin]]'' ku hem fiîl e hem îsm.
== O ==
== P ==
; {{lenger|paşgir}}[[paşgir]]
; {{lenger|paşvesazî}}[[paşvesazî]]
: Çêkirina peyvên nû bi kurtkirina peyvên heyî. Nimûne ''[[ajo]]'' ji ''[[ajotin]]''.
; {{lenger|pêk}}{{lenger|pêkhatî}}[[pêkhatî]], ''pêk''
: Çêkirina peyvên nû bi lihevanîna du an zêdetir peyvan.
; {{lenger|pêşgir}}[[pêşgir]]
; {{lenger|pirjimar}}[[pirjimar]]
; {{lenger|pirtik}}[[pirtik]]
; {{lenger|purîzm}}[[purîzm]]
: Kelîmeyên ku bi armanca [[:w:purîzma zimanî|purîzma zimanî]] ji bo terikandina kelîmeyên qedîm û yên deynkirî tên bikaranîn.
== Q ==
; {{lenger|qusandin}}[[qusandin]]
: Kurtkirina peyvekê bi avêtina çend herfan e û maneya peyvê naguherîne. Nimûne ''[[Miho]]'' ji ''[[Mihemmed]]''.
== R ==
; {{lenger|redûblîkasyon}}[[redûblîkasyon]], ''[[:en:Appendix:Glossary#reduplication|reduplication]]''
: Çêkirina kelîmeyekê bi dubarekirina kelîmeyekê an parçeyekî wê. Nimûne ''hêdî hêdî''.
; {{lenger|rengdêr}}[[rengdêr]]
== S ==
; {{lenger|serenav}}[[serenav]]
: Cureyekî navdêran e ku îşaretê peyvên yekane mîna ''[[Iqlîdes]]'', ''[[Mihemed]]'' dike. Serenav di kurmancî de bi herfên [[girek]] tên nivîsîn.
; {{lenger|sic}}[[sic]]
: Hokereke latînî ku tê maneya "lewma". Bi edetê di nav {{kod|<nowiki>[</nowiki>}} de tê nivîsin û mirov dixwaze bibêje ku ew peyv ne xeletiya kesa ku nivîsekê kopî dike ye, nivîs ji orîjînalê bêyî guhartinê hatiye wergirtin.
; {{lenger|sormancî}}[[sormancî]]
: Tevlihevkirina soranî û kurmancî di zimanê nivîskî de, xisusen bikaranîna kelîmeyên soranî di kurmancî de yan jî yên kurmancî di soranî de ku xelkê kurmancîziman û soranîziman nizanin.
== Ş ==
== T ==
; {{lenger|tebîr}}[[tebîr]], [[#biwêj|biwêj]]
; {{lenger|tewandin}}[[tewandin]]
; {{lenger|tewîn}}[[tewîn]]
; {{lenger|têkilkirin}}[[têkilkirin]]
: Çêkirina peyvekê bi riya tevlîkirina du peyvan. Nimûne ''[[tirkmancî]]'' ji ''[[tirkî]] + [[kurmancî]]''.
; {{lenger|translîterasyon}}[[translîterasyon]], ''[[:en:Appendix:Glossary#transliteration|transliteration]]''
: Veguhastina kelîmeyekê ji alfabeyekê bo alfabeyeke din, herf bi herf. Nimûne {{q|fa|خانه}} → ''xane''.
== U ==
== Û ==
; {{lenger|ûnîverbasyon}}[[ûnîverbasyon]], ''[[:en:Appendix:Glossary#univerbation|univerbation]]''
: Çêbûna çend kelîmeyên ku pir caran li pey hev tên wek kelîmeyeke yekane. Nimûne ''[[îro]]'' ji ''ev ro'', ''[[îsal]]'' ji ''ev sal''.
== V ==
== W ==
; {{lenger|wergirtin}}[[Wîkîferheng:Etîmolojî#Wergirtina wateyê|wergirtina wateyê]], ''wergirtin''
: Berfirehtirkirina wateya peyveke heyî bi riya wergerandina ji zimanekî din.
== X ==
; {{lenger|xweşbêjî}}[[xweşbêjî]] an {{mj|[[edeb]]î [[kelam]]}}
: Peyv û derbirînên li şûna peyveke din ji bo veşartina maneya nexweş an bişerm tên bikaranîn.
== Y ==
; {{lenger|yekser}}[[wergerandina yekser]], ''yekser''
:Çêkirina peyveke nû bi riya wergerandina ji zimanekî din.
== Z ==
; {{lenger|zayend}}[[zayend]]
: Awayekî sinifandina [[#navdêr|navdêran]] di zimanê kurdî û hin zimanên din de ye. Sê cure zayend ''[[#mê|mê]], [[#nêr|nêr]] û [[#mê û nêr|mê û nêr]]'' di zimanê kurdî de hene. Zayenda [[#nêtar|nêtar]] di hin zimanên din hene lê di kurdî de tune.
; {{lenger|zemîr}}[[zemîr]], [[#cînav|cînav]]
; {{lenger|zerf}}[[zerf]], [[#hoker]]
kopt1dakpyud7dq4lyxek31vu4hujc6
Gotûbêja bikarhêner:Wikihez
3
977511
6535051
6347132
2026-09-21T09:10:26Z
Hiyuune
12675
/* babehoods */ new topic ([[mw:c:Special:MyLanguage/User:JWBTH/CD|CD]])
6535051
wikitext
text/x-wiki
== Astengî Bi Sepana Lingua Libre. ==
Silav Birêz,
Vaye 2-3 roj e ku ez nikarim dengê xwe bi rêya sepana Lingua Libre'yê ve biweşînim. Ez dikarim têkevim û deng tomar bikim, lê nikarim dengê xwe li ser Wikimedia Commons biweşînim. Gava ku ez pêl li ser bişkoka "Li Ser Wikimedia Commons Biweşîne" dikim qebûl nake û bişkoka "Dubare Biceribîne Barkirin Bi Ser Neket" dide nîşan. Gava ez pêlê wê jî dikim, tiştek nabe û ev bişkok dîsa derkevî ser holê. Tû kar nake. Derbarê vî mijarê min li ser tora Lingua Libre nivîsand, lê tû encam derneket. Ev program dengê min tomar dike, le naweşîne. Gelo tû fikr û ramanê te li ser vî mijarê heye? Sipas. [[Bikarhêner:Key Mîrza|Key Mîrza]] ([[Gotûbêja bikarhêner:Key Mîrza|gotûbêj]]) 23:38, 28 kanûna paşîn 2023 (UTC)
== Modul û şablonên Wîkîpediya yên Zazakî ==
Silav birêz Balyozxane!
Tu dikarî di Wîkîpediya Zazakî de alîkariya min bikî? Li wir kesek neqeydkirî hin modul û şablon guhert. Ji ber vê yekê agahî tevlihev dibin. Min ew vegerand, lê ew wek nimûne di "Nıfus" ([https://diq.wikipedia.org/wiki/Modul:N%C4%B1fus Modul:Nıfus] û [https://diq.wikipedia.org/wiki/%C5%9Eablon:N%C4%B1fus Şablon:Nıfus]) de xuya nake. Ez berê spas dikim! -- [[Bikarhêner:Mirzali|Mirzali]] ([[Gotûbêja bikarhêner:Mirzali|gotûbêj]]) 19:50, 21 gulan 2023 (UTC)
:@[[Bikarhêner:Mirzali|Mirzali]] Silav. Problemeke gelemper li wîkiyê heye. Bnr. [[:w:en:Wikipedia:Village_pump_(technical)#Graph_extension_disabled_per_immediate_effect|vê gotûbêja en.wîkiyê]]. Grafîk niha naxebite heta xwediyên wîkiyê çareser bike. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 05:46, 22 gulan 2023 (UTC)
:: Spas ji bo pêşniyarê! -- [[Bikarhêner:Mirzali|Mirzali]] ([[Gotûbêja bikarhêner:Mirzali|gotûbêj]]) 06:12, 22 gulan 2023 (UTC)
== Li ser pêwîstiya sê çavkaniyan ji bo nivîsên etîmolojiya peyvên kurdî. ==
Silav Birêz,
Bo çin nivîsandina etîmolojiya peyvên Kurdî evqas erzan e. Çima rêzika bo afirandina peyveke nû bo etîmolojî jî derbas nabe? Ma çawa birêz "Ferhengvan" li gorê kêfa xwe etîmolojiya peyvan dinivîse. Dema ez ji wî çavkanî daxwaz dikim yan qet nayînî yan jî tenê yek çavkanî nîşan dide û ku meriv nikarî çavkanîyê bibînî û peyt bikî
Bo çin nivîsandina etîmolojiya peyvên Kurdî evqas erzan e. Çima rêzikên afirandina peyveke nû, bo nivîsa etîmolojî jî derbas nabe? Ma çawa birêz "Ferhengvan" li gorê kêfa xwe etîmolojiya peyvan dinivîse? Dema ku ez ji wî çavkanî daxwaz dikim yan qet nayînî yan jî tenê yek çavkanî nîşan dide û ku meriv nikarî ew çavkanîyê bibînî û peyt bikî. Ma tenê çavkaniyek têr e? Yan gerek sisê bibe? Ger tu bawer nakî ez dikarim çendîn nimune nîşanê te bidim. Benda bersiva cenabê te me. Sipas [[Bikarhêner:Ebulfîda|Ebulfîda]] ([[Gotûbêja bikarhêner:Ebulfîda|gotûbêj]]) 08:59, 6 hezîran 2023 (UTC)
:Silav @[[Bikarhêner:Ebulfîda|Ebulfîda]]. Tu hinek bi awayekî şovenîst tevdigerî. Hinek bêhnfireh be ji kerema xwe re. Tu çavkanî dixwazî lê belê çavkanî kêm in. Yên heyî jî tu qebûl nakî. Min ji te re Chyet wek referans da, te ew jî qebûl nekir. Chyet nêzî 40 zimanan dizane û hemû zaravayên kurdî jî dizane dîsa jî tu qebûl nakî. Tu wekî folk etymology difikirî û dixwazî her tiştî bikî kurdî. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 08:37, 8 hezîran 2023 (UTC)
::Sipas, Ma tu jî şovenê hevalê xwe nînî? Bo çin 3 referans bo her kes nameşe? [[Bikarhêner:Ebulfîda|Ebulfîda]] ([[Gotûbêja bikarhêner:Ebulfîda|gotûbêj]]) 19:33, 8 hezîran 2023 (UTC)
:::Xwedê hiş daye mirov ku bi kar bînî:
:::1-Çawa peyva "Befr", "Baran" ya Kurdî/Îranî heye lê "Cemed"a Kurdî/Îranî tineye? Dibe? Hemû bi hev re girêdayî nînin? Pirtirîn dewletên ereban da "cemed" çênabe. Ma çima reha/koka cemed "cam" ne bî? Ma hemû zimanzanan û etîmologan ser rêya rast in? Li gor min na. Ez zimanzanek wek "Garnik S. Asatrian" û "David Neil MacKenzie" nas nakim. Min jiyana wan xwendiye her du jî dijminatiya Kurdan kirine:
:::https://www.quora.com/Do-you-believe-that-all-Kurdish-linguists-to-date-have-acted-impartially-and-scientifically/answer/Hamdi-Aciksoz
:::2- Ger peyva "Evîn"a Kurdî tinebe, gelo Kurd ji Ereban hînê hezkirinê bûne? [[Bikarhêner:Ebulfîda|Ebulfîda]] ([[Gotûbêja bikarhêner:Ebulfîda|gotûbêj]]) 19:50, 8 hezîran 2023 (UTC)
== Derbarê pêwistiya 3 referans bo afirandina peyvên nû û nivîsên etîmolojiyên nû. ==
Birêz, Êdî ez westiyam. Cara dawî ji te û hemû nivîskaran rica dikim ku bêyî 3 referans peyvên nû neafirînin û etîmolojî nenivîsînin. Ger ez rastîyê nivîsek/referansek bêyî 3 referans bikevim ezê bi hirs û dijûn livir derkevim. Ji we rica dikim mercên xwe bi kar bînin.[[Bikarhêner:Ebulfîda|Ebulfîda]] ([[Gotûbêja bikarhêner:Ebulfîda|gotûbêj]]) 17:42, 10 hezîran 2023 (UTC)
:Qaîdeyeke ji bo etîmolojiyê wekî ku tu dibêjî tune ye. Ger dixwazî, dikarî dengdaneke bidî destpêkirin û li gorî encama dengdanê em dikarin bixebitin. Bi xatirê te. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 11:21, 14 hezîran 2023 (UTC)
== Çêkirina botekê ==
Merheba hêja {{r|Balyozxane}} Ez bi şanazî dibînim ku tu bota "Balyozbot" bo zêdekirina rûpelên nû ji beşa wergeran bi kar tînî. Ev karekî pir baş e, siheta te xweş! Gelo ez jî dikarim bi awayekî wisa bibim alîkar? Bi silavên germ re! --[[Bikarhêner:Ferhengvan|Ferhengvan]] ([[Gotûbêja bikarhêner:Ferhengvan|gotûbêj]]) 07:00, 9 tebax 2023 (UTC)
:@[[Bikarhêner:Ferhengvan|Ferhengvan]] Balyozbot niha bi otomatîk naxebite, Ez li ser scrîptekê dixebitim ya ku dikare hemû wergerê carekê de çêke. Ger rast bixebite, ez ê skrîptê bidim te. Lê niha hin pirsgirêk çêdibin. Wisa dixuye ku ew ê çênebe. Lê dîsa jî hewl didim.
:::P.S. Tu dikarî [[:w:WP:AWB]] biceribînî. Heger hinek hîn bibî, dikarî hesabekî botê çêkî û ez dikarim alîkariya te bikim.
:[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 13:29, 9 tebax 2023 (UTC)
::Silav @[[Bikarhêner:Ghybu|Ghybu]] Çêkirina gelek rûpelan bi butonekê qedexe ye yan na? [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 17:03, 9 tebax 2023 (UTC)
:::Silav, ne qedexe ye. Piraniya rupelên fr.wiki û en.wiki bi botê hatine çêkirin. Tenê ''[//meta.wikimedia.org/wiki/Copyright copyright]'' û ''[//meta.wikimedia.org/wiki/Bot_policy Bot policy]'' dîqet bikin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:14, 9 tebax 2023 (UTC)
::Gelek spas bo xebata te ya hêja! [[Bikarhêner:Ferhengvan|Ferhengvan]] ([[Gotûbêja bikarhêner:Ferhengvan|gotûbêj]]) 18:35, 9 tebax 2023 (UTC)
:::@[[Bikarhêner:Ghybu|Ghybu]] Ez behsa botekê nakim. Nimûne Ez dikarim bi hesaba xwe ya Balyozxane gelek rûpel carekê de çêkim bi skrîpta [[User:Balyozbot/CreateTranslation.js]]? Dema pêlî "Hemû çêke" dikî, skrîpt yek bi yek hemû rûpelên wergeran çêdike. Ew tiştek normal e? [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 18:37, 9 tebax 2023 (UTC)
::::Nimûne: [[Taybet:Beşdarî/Balyozxane|2 deqe 34 rûpel hatin çêkirin bi skrîptê]] [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 18:49, 9 tebax 2023 (UTC)
:::::Tu hesabê xwe wekî botê bikar tinî û leza guherandina botê hindik bilind e (>12 guherandin/deq). Bi min, bo guherandinên biçûk pirsgirêk tine ye... Feqet tu nikarî tenê carekê de pênc sed an hezar gotar bi vî avayî çêkî :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:50, 9 tebax 2023 (UTC)
::::::@[[Bikarhêner:Ghybu|Ghybu]] çima na yaw bazarî tune ye? {{=D}} Bi hesabê xwe deqê de çend gotar bi vî awayî çêkim, ne pirsgirêk e? Ne tenê ez, ez ê bidime Ferhengvanî jî :D [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 21:16, 9 tebax 2023 (UTC)
:::::::Bnr. [https://ku.wiktionary.org/wiki/Got%C3%BBb%C3%AAja_bikarh%C3%AAner:%C5%9E%C3%AAr#Nochmal_wegen_Deinem_Bot] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:23, 9 tebax 2023 (UTC)
::::::::@[[Bikarhêner:Ghybu|Ghybu]] Li gorî Bot policy 12 edits per minute ji bo botekê baş e. Lê tiştek li ser bikarhêneran nayê gotin. Em ji deqê de 12 rûpel çêkin pirsgirêk çêbibe? [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 21:38, 9 tebax 2023 (UTC)
:::::::::Min ne got ku ew problem e ji ber ku li vira kes ne dijî ye (wikiyên din eleqedar nake). Dîsa jî, belkî, li ser Dîwanê ji bo bikaranîna amûrê dengek pêwîst e {{=)}} [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:49, 9 tebax 2023 (UTC)
::::::::::PS: Problem tenê ji bo [[Taybet:Guherandinên dawî]] ye (nayê xwandin) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:56, 9 tebax 2023 (UTC)
:::::::::::Li ser Wergerê: bi min dema ku hemû beşa Wergerê li ser en.wiki tê kopî kirin, nivîsanda çavkanîyê jî pêwîst e, wekî: [//ku.wiktionary.org/w/index.php?title=xwarin&diff=5207568&oldid=5207001]. Na? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:13, 9 tebax 2023 (UTC)
::::::::::::bi ya min dengdan ne hewce ye lê ger tu bêjî hewce ye, em dikarin yekî çêkin :/
::::::::::::ji bo guherandinên dawî min etiketek çêkir, nimûne [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Balyozxane/testinggg&action=history] tu dikarî guhartinên dawî parzûn bikî ji bo ku guhartinên bi vî etîketî neyên dîtin.
::::::::::::Belê çavkanîdayîn jî pêwîst e. Ez ê wê jî tevlî skrîptê bikim. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 22:32, 9 tebax 2023 (UTC)
{{outdent|::::::::::::}} {{silav|Ferhengvan}}. Min koda çêkirina wergeran nû kir. Êdî ne hewce ye yek bi yek pê li lînkan bikî. Pê li "Hemû çêke" bike. "Cureyê peyvê hilbijêre", Maneya wergerê binivîse ( bi otomatîkî navê rûpelê tê lê dikarî tiştên din lê zêde bikî), dûre ger dixwazî "Etîket" lê zêde bike bike "element, anatomî, biyolojî, ekonomî, hwd.) Dûre pê li "Bişîne" bike. Hemû werger yek bi yek tên çêkirin. Di deqiqeyê de herî zêde 6 rûpel çêdike loma hinek hêdî ye. @[[User:Ghybu|Ghbu]] Çiqas bilez be ne pirsgirêk e? --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 13:22, 11 tebax 2023 (UTC)
:Ji bo veşartina rûpelên ku bi CreateTranslation hatine çêkirin di rûpela Guhartinên dawî de pê li "Nîşankirin" bikin, dûre Pê li "Yên bijartî derxe" bikin û "CreateTranslation hilbijêrin. Bi vî awayî vedişêrin. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 13:24, 11 tebax 2023 (UTC)
::Her hebî, pir baş çêdike. Wisa here, em ê di çend mehan de bigihin milyonek rûpelan :-) [[Bikarhêner:Ferhengvan|Ferhengvan]] ([[Gotûbêja bikarhêner:Ferhengvan|gotûbêj]]) 14:07, 11 tebax 2023 (UTC)
:::@[[Bikarhêner:Ferhengvan|Ferhengvan]] înşela :) lê divê em hay jê hebin rûpelên xelet çênekin, dema çêdikî maneya peyvê bi eşkere bidin nivîsîn [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 18:17, 11 tebax 2023 (UTC)
::::@[[Bikarhêner:Ferhengvan|Ferhengvan]] herwiha ger te xeletîyek dît, min agahdar bike. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 18:18, 11 tebax 2023 (UTC)
::::Merheba, ji cûrên peyvan (navdêr, rengdêr, lêker...) "hejmar" kêm e, divê ew jî hebe. [[Bikarhêner:Ferhengvan|Ferhengvan]] ([[Gotûbêja bikarhêner:Ferhengvan|gotûbêj]]) 06:29, 12 tebax 2023 (UTC)
* @[[Bikarhêner:Ferhengvan|Ferhengvan]] di vê navberê de ger wextê te hebe, Xêra xwe dikarî navê zimanan jî bikî kurdî [[Modul:ziman]], ji ber ku em gelek rûpel çêdikin navên zimanan bi îngilîzî dimînin. Divê werin kurdîkirin ji bo ku em karibin kategoriyên wan çêkin.
Ji bo çêkirina kategoriyan Koda jêr tevlî [[Special:MyPage/common.js|rûpela xwe ya common.js]] bike:
<syntaxhighlight lang="javascript>
//[[Bikarhêner:Balyozxane/addAutoCat.js]]
mw.loader.load('//ku.wiktionary.org/w/index.php?title=Bikarhêner:Balyozxane/addAutoCat.js&action=raw&ctype=text/javascript');
</syntaxhighlight>
:--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 06:39, 12 tebax 2023 (UTC)
::{{r|Ferhengvan}} Rûpelên xelet çêneke. Min hejmar lê zêde kir lê dema tiştek kêm be heta were sererastkirin bisekine Xêra xwe. --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 06:39, 12 tebax 2023 (UTC)
:::Temam. Lê yên çêkirî ne xelet in ji ber ku hemû hejmar di heman demê de navdêr in jî. [[Bikarhêner:Ferhengvan|Ferhengvan]] ([[Gotûbêja bikarhêner:Ferhengvan|gotûbêj]]) 06:51, 12 tebax 2023 (UTC)
::::Silav @[[Bikarhêner:Ghybu|Ghybu]]. Em çêkirina rûpelan bi hesaba xwe dewam bikin yan jî hesaba xwe ya botê bi kar bînin? pîng {{r|Ferhengvan}} [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 19:37, 14 tebax 2023 (UTC)
== Bot ==
Silav Balyozxane, Sererastkirina rêzimanî bi botê problem e: te jî [https://ku.wiktionary.org/w/index.php?title=Koban%C3%AE&curid=279200&diff=5551838&oldid=5551836 dît], ji ber ku jêgirtin û çavkaniyan jî sererast dike :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:48, 21 îlon 2023 (UTC)
:Silav @[[Bikarhêner:Ghybu|Ghybu]] Min yek bi yek AWB kontrol dikir û wextê xeletî çêkir, min rawestand, xeletiyê rast kir û dîsa dewam kir :) [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 14:50, 21 îlon 2023 (UTC)
== çêkirina forman bo [[hûr kirin]] ==
Silav Balyozxane,
gelo çi dibe sedem ku forma [[hûr kir]] bi awayekî otomatîk nayê afirandin? --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 11:15, 12 çiriya paşîn 2023 (UTC)
:Silav @[[Bikarhêner:Şêr|Şêr]]. Li cem min pirsgirêk tine ye. Ka rûpelê purge bike û dîsa biceribîne. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 11:18, 12 çiriya paşîn 2023 (UTC)
::Silav, niha, piştî ku te rêz guhart, çêbû. - [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 11:20, 12 çiriya paşîn 2023 (UTC)
:::Û gelek caran divê ez ji peyvê derkevim ji ber ku form amade nabin (dema ku ez pê lê dikim). Bnr.: [https://ku.wiktionary.org/w/index.php?title=d%C3%BBr_kir&action=edit&redlink=1&editintro=User:Balyozxane/creation/intro&accel_lang=ku&accel_lemma=d%C3%BBr%20kirin&accel1=pos-l%C3%AAker%20bor%C3%AE%7C1%7Cyj-form-of&accel2=pos-l%C3%AAker%20bor%C3%AE%7C2%7Cyj-form-of&accel3=pos-l%C3%AAker%20bor%C3%AE%7C3%7Cyj-form-of&accel4=pos-l%C3%AAker%20bor%C3%AE%7C123%7Cp-form-of]. -- [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 11:21, 12 çiriya paşîn 2023 (UTC)
::: Gava ez ji formê dertêm, û dîsa li ser gotara sereke dîsa pê li formê bikim, hingê çêdibe. --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 11:23, 12 çiriya paşîn 2023 (UTC)
::::@[[Bikarhêner:Şêr|Şêr]] Ger li jor URL hebe û tê de agahdarî hebin, wê demê ne hewce ye derkevî rûpelê dîsa bar bike ew ê were çêkirin. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 11:37, 12 çiriya paşîn 2023 (UTC)
:::::Baş e, mala te ava. :) Dem baş --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 11:38, 12 çiriya paşîn 2023 (UTC)
::::::URL bi piranî tune. --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 11:42, 12 çiriya paşîn 2023 (UTC)
:::::::@[[Bikarhêner:Şêr|Şêr]] Ez lê dinêrim lê min ti pirsgirêk nedît. Belkî tu hinek zû tevdigerî û bêyî ku lînk were derdikevî? [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 12:17, 12 çiriya paşîn 2023 (UTC)
::::::::@[[Bikarhêner:Şêr|Şêr]] Hay ji syntaxhighlighter hebe, dema vekirî be carinan ji ber wê encam dernakevin. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 17:26, 12 çiriya paşîn 2023 (UTC)
== amûra "wergerîne" ==
Silav keko,
min hewl da ku wergeran (ji ferhenga en) bidim wergirtin bi rêya amûra te, lê nebû. :( --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 20:18, 16 çiriya paşîn 2023 (UTC)
:@[[Bikarhêner:Şêr|Şêr]] Silav. Te çi kir? Divê ji en.wiktê beşa Translation rasterast li rûpeleke ku.wiktê kopî bikî. Ti tişt neguherîne. Divê şablonên <nowiki>{{trans-top}} û {{trans-bottom}}</nowiki> hebe. (Heke navê şablona trans-top-see be divê -see jê bibî.) Dûre pê li Wergerîne bike û navê rûpela en.wiktê binivîse. Tiştek din nenivîse. Ew qas :) [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 20:21, 16 çiriya paşîn 2023 (UTC)
::@[[Bikarhêner:Şêr|Şêr]] Di amûra CreateTranslation de piştî te pê li "Bişîne" kir, divê rûpel vekirî bimîne heta hemû werger werin çêkirin. Ger ji rûpelê derkevî, amûr betal dibe. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 20:32, 16 çiriya paşîn 2023 (UTC)
::Spas, fam kir ;)
:::PS: qismê > beşa çêtir e, ji ber ku beş "kurdîtir" e. Silav --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 20:32, 16 çiriya paşîn 2023 (UTC)
:::@[[Bikarhêner:Şêr|Şêr]] "beş" li vir wekî hemû beşa Wergerê karibe were fêmkirin, lê di beşekê de dikare ji yekî zêdetir qism hebin loma me peyva qism tercîh kir. Herwiha ji mêj ve ye me dev ji dîtina peyveke "kurdîtir" berdaye, kîjan peyv berbelav be em wê bi kar tînin. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 20:39, 16 çiriya paşîn 2023 (UTC)
::::Ez jî wisa difikirim. Lê mixabin hin kurdên me serê xwe bi "paqijkirina" kurdî diêşînin. Ger em li gorî vê mantiqê tev bigerin, divê em nîvê zimanê almanî jê bibin. :)) --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 20:47, 16 çiriya paşîn 2023 (UTC)
:::::@[[Bikarhêner:Şêr|Şêr]] Va ye dîsa em ketin vê nîqaşê [[:w:Gotûbêj:Bangeşe#Barkirina_Bangeşe_12-2023]]. Xwedê ez ê dîn bibim. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 18:05, 10 kanûna pêşîn 2023 (UTC)
::::::Herwiha [[:w:Pêjimêrk]] {{dîwar}} [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 18:06, 10 kanûna pêşîn 2023 (UTC)
::::::Bi demê re gotinên biyanî tevkariyeke girîng li zimanê kurdî kirine û bûne beşekî ji wê. Ev peyv danûstandina çandî, pêşketina zanistî û bandorên din nîşan didin. Zimanekî bê peyvên biyanî ne tenê ji aliyê zimanî ve belengaz e, di heman demê de ji aliyê çandî ve jî îzole dibe û nikare xwe adapte bike. Tê bêjî qey 5 hezar peyvên latînî wê kurdî xira bikin. :) Serê xwe bi wan neêşîne. Bawer bike mirov newêre kurdên me hînî kurdî bike. Wek mînak gava ez bêjim kursî, wê tiliyên xwe di nav çavên min re rakin. :( Wê li dewsa wê bêjin "rûniştek", lê ev peyv ji bo her tiştê ku mirov li ser "rûdinê" tine ziman. Xeman nexwe, yên wisa wê xilas nebin :-) -- [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 18:29, 10 kanûna pêşîn 2023 (UTC)
:::::::@[[Bikarhêner:Şêr|Şêr]] Erê lê pirsgirêk ew ê fêmbariya wîkîpediya xira dibe. Loma dixwazim qet nebe tiştên wekî sernavên gotar û kategoriyan baş bin lê ew jî nabe. Ew kes timî bi peyvên ecêb dinivîse ku êdî meriv jê ti tişt fêm nake. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 18:31, 10 kanûna pêşîn 2023 (UTC)
::::::::Ez jî li aliyê te me. Lê bawer ke wî hevalî ji ev peyv ji yekî wek "xwe" hîn bûne. Ev şaşî ne ya wî ye. Gava ew jî van xeletiyan bidomîne, wê yên piştî wî jî wisa hîn bibin. :( --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 18:35, 10 kanûna pêşîn 2023 (UTC)
== Forma nû a çêkirina peyvan zû bar dibe ==
Silav Balyozxane,
bi min forma nû a (wek ya viyetnamî) zû bar dibe. Dema ez dixwazim formên tewandî yên navdêran çêbikim, formên skrîptê bar nabin dema ku ez forma kesk ya tabloya tewandinê ditikînim.. --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 15:55, 1 kanûna pêşîn 2023 (UTC)
:[https://ku.wiktionary.org/w/index.php?title=avaker%C3%AAn&action=edit&redlink=1 Mînak] --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 15:56, 1 kanûna pêşîn 2023 (UTC)
::Silav @[[Bikarhêner:Şêr|Şêr]] Dema di URLyê de ''User:Balyozxane/creation/intro'' [https://ku.wiktionary.org/w/index.php?title=avaker%C3%AAn&action=edit&redlink=1&editintro=User:Balyozxane/creation/intro&accel_lang=ku&accel_lemma=avaker&accel1=pos-navd%C3%AAr%20def%7Cconstr%7Cp-form-of%20gender-n] hebe li cem min form bar nabe. Lê wekî din her tim bar dibe. Ger karekî te bi vî amûrî tinebe dikarî li ser [[Taybet:Tercîh#mw-prefsection-gadgets]] neçalak bikî. Li qutiya li kêleka "Forma çêkirina rûpelên nû çalak bike" bitikîne ew ê were '''ne'''çalakkirin. Dûre dikarî dîsa çalak bikî. Niha çareseriyeke din nabînim. Ger fikrekî te hebe, bibêje em biceribînin. [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 16:06, 1 kanûna pêşîn 2023 (UTC)
:::Spas, min ne-çalak kirin, pirsgirêk çareser bû. --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 16:09, 1 kanûna pêşîn 2023 (UTC)
== çêkirina kategoriyên tên xwestin ==
Silav Balyozxane,
ez [https://ku.wiktionary.org/wiki/Taybet:Kategor%C3%AEy%C3%AAn_t%C3%AAn_xwestin van] kategoriyên xwestî bi bo çêbikim? Min kategoriyek wek test çêkir, [https://ku.wiktionary.org/wiki/Kategor%C3%AE:Peyv%C3%AAn_kurd%C3%AE_bi_pa%C5%9Fgira_-ker%C3%AE li vir bnr] -- [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 21:26, 2 kanûna pêşîn 2023 (UTC)
:Silav @[[Bikarhêner:Şêr|Şêr]]. Ger dixwazî, çêke lê pêşî [[WF:SB#katbike-rec]] bi kar bîne wek "pwb.py katbike-rec -wantedcategories". (Ne hewce ye lîsteyekî çêkî) Daku jorkategorî û înterwîkî lê were zêdekirin. Piştî hemû xilas bû îcar bi [[WF:SB#katbike]] dîsa hemû -wantedcategories çêke. Spas! [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 21:30, 2 kanûna pêşîn 2023 (UTC)
::Temam, spas, va min dest pê kir [https://ku.wiktionary.org/wiki/Kategor%C3%AE:Peyv%C3%AAn_kurd%C3%AE_bi_pa%C5%9Fgira_-ok%C3%AE]. Ez ê vêga bi hesabê bot bidim çêkirin. Êvara te bimîne xweş, û spas ji bo keda te. --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 21:46, 2 kanûna pêşîn 2023 (UTC)
:::@[[Bikarhêner:Şêr|Şêr]] Gelek spas! A te jî bimîne xweş :) [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 21:49, 2 kanûna pêşîn 2023 (UTC)
== Speech recognition ji bo kurmancî ==
Silav bira,
te [https://huggingface.co/Akashpb13/xlsr_kurmanji_kurdish ev] dîtiye? Xuya ye ku nû ji bo kurmancî hatiye çêkirin. Piraniya hevokan hema bêje ji sedî 90 nas dike --17:50, 7 kanûna paşîn 2024 (UTC) [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 17:50, 7 kanûna paşîn 2024 (UTC)
:@[[Bikarhêner:Şêr|Şêr]] ne welle min nedîtiye û zêde bala min tiştên wisa nakişîne lê belê hevalê @[[Bikarhêner:Guherto|Guherto]] pê eleqedar dibe, ez ê jê re bişînim. Gelek spas! [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 17:59, 7 kanûna paşîn 2024 (UTC)
::Ev tişt bala min dikişînin. :)) Eger pirsên Guherto hebin, dikare bi min re bikeve têkiliyê. Êvara te bimîne xweş :) --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 18:02, 7 kanûna paşîn 2024 (UTC)
:::@[[Bikarhêner:Şêr|Şêr]] Ew ê dê gelek bextiyar be :) Êvar baş! [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 18:05, 7 kanûna paşîn 2024 (UTC)
::::{{r|Şêr}} Di sala 2023an li ser ''commonvoice.mozilla'' de zêdetirî 15 hezar devokên kurdî hatine qeyd kirin, bnr https://commonvoice.mozilla.org/kmr. Berê me nikarîbû bikin ji ber ku dane kêm bûn, niha bi ''[[:w:en:Artificial neural network]]'' bi hêsanî tê çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:25, 7 kanûna paşîn 2024 (UTC)
:::::Silav Ghybu, gelek spas ji bo agahiyê, bo keda we. Jixwe li wir Mozilla Commonvoice jî wek çavkanî hatiye bikaranîn. Silav û rêz. --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 20:13, 7 kanûna paşîn 2024 (UTC)
::::::[https://huggingface.co/facebook/mms-tts-kmr-script_latin?text=Ew+%C3%AEro+%C3%A7i+dike%3F Ev jî] nivîsê dike deng. :) --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 20:28, 9 kanûna paşîn 2024 (UTC)
:::::::@[[Bikarhêner:Şêr|Şêr]] Te ceriband? Xweş dixebite gelo? Belkî em dikarin gotarên Wîkîpediyayê bidin xwendin û lînka dengî li rûpelan zêde bikin :) [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 20:31, 9 kanûna paşîn 2024 (UTC)
::::::::Min hê îro dît. Li gorî testên min nîvî-nîvî ye. Modela deng di hin peyvan xwe pirsgirêkan derdixe, zêde "qeba" ye, û hin tîp tên daqurtandin (bi lêv nake). [Û her wiha]Pêdiviya me bi zimanekî naziktir heye. :)) Model divê hinekî were perwerdekirin. Ji bo niha ez bikaranîna wê pêşniyar nakim bi rastî.- Silav û rêz ..[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 20:40, 9 kanûna paşîn 2024 (UTC)
::::::::Ez ê li ser Google Colab, modelê daxim û test bikim. Lê tu jî diarî peyvên xwe an jî cumleyên/hevokên xwe binivîsî. :) Wê gavê ne hewce ye ku tu daxî. :)) --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 20:42, 9 kanûna paşîn 2024 (UTC)
:::::::::Gelek spas! Ez ê li ser PAWS biceribînim [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 20:48, 9 kanûna paşîn 2024 (UTC)
::::::::::Merheba ji we re hevalno. Min hê li ew tiştê ko Şêr lînk kiriye neceribandiye lê piçekî tecrûba min bi van tiştan re heye. Wextê ko hê nû Metayê proja xwe ya bi navê "[https://ai.meta.com/blog/multilingual-model-speech-recognition/ MMS]" derxistibû min xwest biceribênim û min dûvre jî ceriband. TTSa wê zêde bi kêr nayê ji ber ko dengên rast dernaxe (li gora alfabê diçe û alfaba me ji bo şanîkirina dengan bi kêr nayê), lê speech-recognitiona wê bi kêr tê. Hinek gotinan peve dinivîsêne lê ew piştre rehet kare were edilandin (bi destan jî û bi otomatîkî jî). Metayê proja NLLByê jî derxistibû û ew jî di mesela tercimekirinê de ji Google Translate gelekî baştir e. (Dîse jî ji bo kurmancî ya herî baş Bing Translate e). Ez bi rastî bêjim ez piçekî bi xwe serbilind im, [[Wîkîferheng:Dîwan/arşîv 2020#CommonVoice bi kurmancî|ji ber ko herî pêşî min ji CommonVoicê xwest ko kurmancî lê zêde bikin]], berî çar salan. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 11:09, 10 kanûna paşîn 2024 (UTC)
:::::::::::@[[Bikarhêner:Şêr|Şêr]] Ew proja ko te lînk kiriye ji MMSa Metayê kevntir e. Berî 2 salan hatiye çêkirin û li vir dosyeyek li serê heye: https://aclanthology.org/2022.lrec-1.689.pdf [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 11:13, 10 kanûna paşîn 2024 (UTC)
== POS-Tag Kurmancî ==
Min tiştekî wisa ya bikêrhatî dît niha: [http://stanza.run/]. --23:33, 13 kanûna paşîn 2024 (UTC) [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 23:33, 13 kanûna paşîn 2024 (UTC)
:@[[Bikarhêner:Şêr|Şêr]] Gelek spas. Min niha ceriband xweş xuya dike :D. Dikarî emaila xwe vekî, dixwazim tiştek li te pirs bikim? [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 23:43, 13 kanûna paşîn 2024 (UTC)
:{{r|Şêr}} Min ê vê amûrê kod bikira, bnr. [//ku.wiktionary.org/wiki/W%C3%AEk%C3%AEferheng:D%C3%AEwan/2023#Am%C3%BBr%C3%AAn_anal%C3%AEzkirina_kurmanc%C3%AE? ''Amûrên analîzkirina kurmancî?''] Ping {{r|Ferhengvan}} [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:47, 13 kanûna paşîn 2024 (UTC)
::{{r|Ghybu}}, {{r|Balyozxane}}, di hin lêkeran de kêmasî hene, lê bi piranî dixebite. Mirov dikare [https://huggingface.co/stanfordnlp/stanza-kmr li vir] pêşniyazan bike? --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 08:03, 14 kanûna paşîn 2024 (UTC)
::{{r|Balyozxane}}, min standina e-peyaman aktîv kir. :) --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 08:07, 14 kanûna paşîn 2024 (UTC)
:::@[[Bikarhêner:Şêr|Şêr]] Min şand, înşela te dîtibe :) [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 13:46, 14 kanûna paşîn 2024 (UTC)
::::Belê, min dît. Lê divê pêşî ez wê sepanokê daxim. :)) --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 14:12, 14 kanûna paşîn 2024 (UTC)
:::::@[[Bikarhêner:Şêr|Şêr]] Temam bira :) [[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 14:12, 14 kanûna paşîn 2024 (UTC)
::::::Min ID şand. ;) --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 14:23, 14 kanûna paşîn 2024 (UTC)
== tîpen erebî ==
: → ''Li rûpela'' [[Wîkîferheng:Dîwan/2024#tîpen erebî]] ''hat barkirin.''
== [[Wîkîferheng:ceribandin/aWa]] ==
null —[[Bikarhêner:Balyozbot|Balyozbot]] ([[Gotûbêja bikarhêner:Balyozbot|gotûbêj]]) 21:05, 19 çiriya paşîn 2024 (UTC)
== [[Wîkîferheng:ceribandin/aWa]] ==
Silav @Balyozxane: Gotara te ya Wîkîferheng:ceribandin/aWa dê were jêbirin ji ber ku bêçavkanî ye —[[Bikarhêner:Balyozbot|Balyozbot]] ([[Gotûbêja bikarhêner:Balyozbot|gotûbêj]]) 21:15, 19 çiriya paşîn 2024 (UTC)
== [[Wîkîferheng:ceribandin/aWa]] ==
Silav @Balyozxane: Gotara te ya Wîkîferheng:ceribandin/aWa dê were jêbirin ji ber ku —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 17:06, 24 çiriya paşîn 2024 (UTC)
== [[Wîkîferheng:ceribandin/aWa]] ==
Silav @Balyozxane: Gotara te ya Wîkîferheng:ceribandin/aWa dê were jêbirin ji ber ku —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 17:07, 24 çiriya paşîn 2024 (UTC)
== [[Wîkîferheng:ceribandin/aWa]] ==
Silav @Balyozxane: Gotara te ya Wîkîferheng:ceribandin/aWa dê were jêbirin ji ber ku bêçavkanî —[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane#top|gotûbêj]]) 17:20, 24 çiriya paşîn 2024 (UTC)
== Files on Commons ==
Hi! I moved most of the files to Commons. So now they can be used on other wikis. The local files in [[:Kategorî:Gotarên ku bên jêbirin]] can now be deleted. Can you have a look? [[Bikarhêner:MGA73|MGA73]] ([[Gotûbêja bikarhêner:MGA73|gotûbêj]]) 16:20, 31 kanûna pêşîn 2024 (UTC)
:@[[Bikarhêner:MGA73|MGA73]] Sorry for the late reply. I've been quite busy lately, your message completely slipped my mind. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 21:53, 13 kanûna paşîn 2025 (UTC)
:: Hello! No worries :-) --[[Bikarhêner:MGA73|MGA73]] ([[Gotûbêja bikarhêner:MGA73|gotûbêj]]) 06:41, 14 kanûna paşîn 2025 (UTC)
I noticed that there is one unused file without a license: [[:Wêne:Wîkîferheng-guhertin.png]]. It it is no longer usable then I suggest to delete it. --[[Bikarhêner:MGA73|MGA73]] ([[Gotûbêja bikarhêner:MGA73|gotûbêj]]) 15:41, 14 kanûna paşîn 2025 (UTC)
:Done, thanks! [[Bikarhêner:Wikihez|Wikihez]]<small><sup>'''[[Bikarhêner:Balyozxane|Balyozxane]]''' ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]])</sup></small> 15:44, 14 kanûna paşîn 2025 (UTC)
::Happy to help. Now [[Special:ListFiles]] looks nice and short :-) --[[Bikarhêner:MGA73|MGA73]] ([[Gotûbêja bikarhêner:MGA73|gotûbêj]]) 15:46, 14 kanûna paşîn 2025 (UTC)
== Kom ==
Silav bira, hê jî ew koma we ya ku tu, Bikarhêner û her wiha Guherto tê de bûn, heye? Ez jî demekê tê de bûm, lê ji ber zanîngehê dema min tune bû, û ez bêdeng û bêseda jê derketibûm. :)) --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 20:38, 7 sibat 2025 (UTC)
:@[[Bikarhêner:Şêr|Şêr]] Erê, Min mesaj şand ka lê binêre :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 21:06, 7 sibat 2025 (UTC)
::@[[Bikarhêner:Wikihez|Wikihez]], niha min dît. :) --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 21:13, 7 sibat 2025 (UTC)
:::Grûba we/me belawela bû? Guharto xuya nake. :( Gelo ew hî jî aktîv e? -- [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 22:08, 29 tebax 2026 (UTC)
::::Tê û diçe.. Kom hîn jî heye, em li hêviya te ne :) wextekî ez jî derketibûm feqat komek nû heye niha. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 22:34, 29 tebax 2026 (UTC)
:::::karî mesajekê ji min re bişînî li ser gmailê. :) -- [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 22:35, 29 tebax 2026 (UTC)
::::::mesaj hat, spas [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 22:39, 29 tebax 2026 (UTC)
:::::::min mesaj şand [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 22:42, 29 tebax 2026 (UTC)
== jêgirtin ==
#* {{jêder-pirtûk|ku|jêgirtin=û |nivîskar=J.R.R. Tolkien|sernav=Hobbit|wergêr=Samî Hêzil|cîh=Enqere|weşanxane={{w|Weşanên Cervantes|Cervantes}}|sal=2023|çap=1|rûpel=9|isbn=9786257724838}}
merhaba. Navê wergêr xuya nake dikarî bidî xuya kirin xêra xwe. [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 19:01, 28 sibat 2025 (UTC)
:Merheba {{r|Rewiye tine}}. Gereg {{param|wergêr}} be te "Wergêr:" nivîsiye.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 19:06, 28 sibat 2025 (UTC)
::@[[Bikarhêner:Rewiye tine|Rewiye tine]] Bira, qaîdeyeke wisa heye ku nimûneyên bikaranîna kelîmeyan gereg pêşî ji nivîskarên bi kurdî nivîsîne were wergirtin, eger çavkaniyên ji kitêbên kurdî tine be, wê demê meriv dikare tercimeyên kitêban ji zimanên din bi kar bîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 19:11, 28 sibat 2025 (UTC)
:::Yanê pirtukên werger ji bo mînak naye qebûl kirin? [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 19:16, 28 sibat 2025 (UTC)
::::@[[Bikarhêner:Rewiye tine|Rewiye tine]] Em nabêjin "nayê qebûlkirin" lê gereg pêşî çavkaniyên kurdî be dûre yên tercime. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 19:23, 28 sibat 2025 (UTC)
:::Rast e. Lê min vê pirtûkê xwendîye û hinek peyva bijartîye. Ez nîyetim wan jêgirtina derbas bikim. Mesele ev e. Berîya vê, min ji pirtûkên klasîk jî jêgirtina derbas kiriye. Ji bo hişyarî mala te ava. [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 19:27, 28 sibat 2025 (UTC)
::::@[[Bikarhêner:Rewiye tine|Rewiye tine]] Yên klasîk pir muhîm in, tê de kîjan kelîme derbas bin gereg em herf bi herf li wir qeyd bikin, lê yên tercime tenê eger çavkaniyên kurdî tinebin baş in. Tu dîsa kelîmeyên ku te bijartine binivîse lê ji bo kelîmeyên naskirî nekî çêtir e. Qerar dîsa jî ya te ye. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 19:32, 28 sibat 2025 (UTC)
== kurmancîyek şkestî ==
Silav {{r|Wikihez}}
https://www.youtube.com/watch?v=hH-DO1RT8zM [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 12:23, 24 adar 2025 (UTC)
== You may be an eligible candidate for the U4C election ==
<div lang="en" dir="ltr" class="mw-content-ltr">
Greetings,
The [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee|Universal Code of Conduct Coordinating Committee (U4C)]] seeks candidates for the 2026 election. The U4C is the global committee responsible for overseeing enforcement of the [[foundation:Special:MyLanguage/Policy:Universal Code of Conduct|Universal Code of Conduct]]. Elections are held annually, if elected a committee member serves for two years.
This year the U4C requires candidates to hold administrator rights on at least one wiki, which is why you are being contacted as you appear to hold this right. There are other requirements, such as candidates must be at least 18 years old and may not be employed by the Wikimedia Foundation or other related chapters and affiliates. You can find more information in the [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Election/2026#Call_for_Candidates|call for candidates on Meta-wiki]]. Additionally, the committee's working language is English; some ability to communicate in English is required.
The election opens on 18 May, if you are eligible and interested you have until 10 May to submit your candidacy. There will be a week in between for candidates to answer questions from the community. Voting takes place privately in [[m:Special:MyLanguage/SecurePoll|SecurePoll]], successful candidates must receive at least 60% support. More information is available on [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Election/2026|the 2026 Elections page]], including timelines and other candidacy information. If you read over the material and consider yourself qualified, please consider submitting your name to run for the committee. If you think someone else in your community might be interested and qualified, please encourage them to run.
In partnership with the U4C -- [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User_talk:Keegan (WMF)|talk]]) 20:06, 28 nîsan 2026 (UTC) </div>
<!-- Message sent by User:Keegan (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=User:Keegan_(WMF)/test&oldid=30472432 -->
== [[bilgîsayar]] ==
Silav @[[Bikarhêner:Wikihez|Wikihez]], di gotara [[bilgîsayar]] de çavkanî hewce ne. ''[[Wîkîferheng:Pîvekên tevlîkirinê|Çavkanî hewce ye]].'' —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 00:11, 30 hezîran 2026 (UTC)
== Lîsteyek heye? ==
Silav WikiHez, te got şablon hene, eceba lîsteyek heye ku ez jê hemû şablonên WikiFerhengê bibînim? [[Bikarhêner:Kurdanparez|Kurdanparez]] ([[Gotûbêja bikarhêner:Kurdanparez|gotûbêj]]) 22:14, 31 tîrmeh 2026 (UTC)
:Merheba @[[Bikarhêner:Kurdanparez|Kurdanparez]], Li vira [[Alîkarî:Bikaranîna şablonan]] lîsteyek heye feqat hem gelek ji wan kevn bûne, hem jî ne mimkûn e em hemû şablonan rêz bikin. Pêşî li ser wîkîya îngilîzî li şablona dixwazî bigere, eger înterwîkiyan de tinebe, ji min re binivîse ez ê çêkim.
:Pirsek Min non-gloss [[:en:T:n-g]] wekî {{ş|îzaha ne eleqedarî maneyê}} (bi kurtî înm) tercime kir feqat çi eleqeyê ez jî nizanim fikrekî te hebe li gotûbêjê binivîse em rast bikin. Niha dibêjim, gloss bikim îzaha maneyî, non-gloss bikim îzaha nemaneyî belkî baştir be, tu çi dibêjî? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 22:22, 31 tîrmeh 2026 (UTC)
::Gelek spas. Ez ê li wir binivîsim. [[Bikarhêner:Kurdanparez|Kurdanparez]] ([[Gotûbêja bikarhêner:Kurdanparez|gotûbêj]]) 22:27, 31 tîrmeh 2026 (UTC)
:::@[[Bikarhêner:Kurdanparez|Kurdanparez]] [https://ku.wiktionary.org/w/index.php?title=dimil%C3%AE&diff=6292587&oldid=6292584] Ji bo înlîne-modifiers bnr [[Modul:columns#L-711]] ji bo etîketan [[Modul:labels#Çêkirina_etîketan]] jkx yên ku kêm in îlawe bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 22:49, 31 tîrmeh 2026 (UTC)
::::Gelek spas :)) [[Bikarhêner:Kurdanparez|Kurdanparez]] ([[Gotûbêja bikarhêner:Kurdanparez|gotûbêj]]) 23:03, 31 tîrmeh 2026 (UTC)
== کرمانجی ==
سەلام. پرسیارێکم هەبوو، کرمانجی زمانە یان زاراوە؟ [[Bikarhêner:SiNa|SiNa]] ([[Gotûbêja bikarhêner:SiNa|gotûbêj]]) 12:34, 19 tebax 2026 (UTC)
:زاراوا یە فەقات ژ بۆ ژ هەڤ ئێجستنا کورمانجی و سۆرانی ل ڤر وەکی زمان خویا دبە [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 12:41, 19 tebax 2026 (UTC)
::باشە لە [https://ku.wiktionary.org/wiki/Destp%C3%AAk دەستپێک] نووسراوە «فەرهەنگ بە '''کورمانجی''' و زێدەیێ ۳۸۱۹ زیمانێن دینێ»، باشتر نەبوو نووسرابا '''کوردی کرمانجی'''؟
::[[:Kategorî:Kurmancî|لێرەش]] لە نێو ئەو باکسەی نووسراوە '''زیمانێ کورمانجی''' نەک '''زاراڤەیا کورمانجی'''. [[Bikarhêner:SiNa|SiNa]] ([[Gotûbêja bikarhêner:SiNa|gotûbêj]]) 19:02, 19 tebax 2026 (UTC)
:::Ew ne tiştên muhîm in, ne lazim e em her daîm kurdî kurmancî binivîsin, kurmancî jî soranî jî kurdî ne, her kes jî wisa qebûl dike. Kategorî jî ji ber ku otomatîk tê çêkirin min nexwest wextê xwe pê re xerc bikim. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 10:51, 21 tebax 2026 (UTC)
== Soranî bi tîpên latînî ==
Silav birako, min dît ku bo eynî/heman kelîmeyên soranî guhartoya bi tîpên latînî jî heye. Û translîterasyon jî tevlihev dibe. Wekî nimûne binêre: [https://ku.wiktionary.org/wiki/b%C3%AErkirdinewe#Soran%C3%AE]. Gelo baş e ku her du guharto hebin (farisî-erêbî û latînî)? Ez newêrim devê mijarê vekim,lê bala min kişand. :)) --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 20:25, 24 tebax 2026 (UTC)
:Merheba, tiştên wisa li [[WF:Dîwan]]ê binivîsî çêtir e, feqat eger tenê fikrê min dipirsî, bi min yên soranî qet ne hewce ne, îro soranî tenê bi '''herfên''' erebî tên nivîsin feqat min kovarên kevn ên 1970î de soranî bi herfên latînî jî dîtine, [https://books.vejin.net/ck/books Vejîn Kitêbekan] jî yên erebî transkrîbeyî latînî dikin. Dîsa jî bila hebin, zerara wan nîne bi şerta ku li vir hatiye dayîn: [[Wîkîferheng:Dîwan/2026#Peyvên_soranî_bi_latînî_(Kategorî:Soranî)]] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 20:31, 24 tebax 2026 (UTC)
::Spas ji bo bersivê. Bi taybetî min ji ber şablonan pirsî, ji ber ku tîp^en ji alfabeya erebî jî tevlî dibin. Wa Ghybu mijar vekiriye. Gava wextê min çêbû. Ez ê cewaba wî bidim. --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 20:36, 24 tebax 2026 (UTC)
:::Bnr [[Modul:rêziman#L-188]] tu dikarî yên bi latînî bi riya <code>lang:findBestScript(pagename)</code> derxî.
:::Li vir[https://ku.wiktionary.org/w/index.php?title=%D9%86%DB%95%D8%B1%D9%85%D8%A7%D9%85%DB%8E%D8%B1&curid=1358825&diff=6342225&oldid=6342224] rênima çi ye? Talîmat e? hejmardarî mihasebe ye? Kesê ku vê ferhengê bi kar tîne çawa ji heqê vî zimanî derkeve? :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 20:56, 24 tebax 2026 (UTC)
::::Ez ê Scriptê biceribînim. Ji ber pirsaa te: Erê, min niha ew guhartin. Ji bo ferhengê wê ferhengeke din lazim be :) -- [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 21:17, 24 tebax 2026 (UTC)
:::::Welleh eynî wisa ye :D Gelek kes ketine derdê dîtina kelîmeya kurdî ya herî orîjînal, ji bîr dikin ka milet wê çawa bike :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 21:20, 24 tebax 2026 (UTC)
::::::Not: Mala te ava :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 21:21, 24 tebax 2026 (UTC)
:::::::Tu çima amûra me ya [[Wf:Nû]] naceribînî? :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 22:58, 24 tebax 2026 (UTC)
::::::::Hmm, ka ez lê binêrim, bê ka ya nû çawa ye. :) -- [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 23:24, 24 tebax 2026 (UTC)
:::::::::Min aktîv kir, gelekî xweşik, lê dirêj e. :) Bi awayekî bidetay hatiye çêkirin. Destên te qet êşê nebînin. -- [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 23:42, 24 tebax 2026 (UTC)
== Citoid ==
Funksiyona CITOIDê gelekî serkeftî ye. Destên te sax bin û qet êşan nebînin :) --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 17:23, 29 tebax 2026 (UTC)
:Mala te ava, destê Claude sax be :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 17:30, 29 tebax 2026 (UTC)
::Deqe 1 gol 1 [https://ku.wiktionary.org/w/index.php?title=qaql%C3%AEbaz&curid=47734&diff=6347006&oldid=6315450] {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 17:31, 29 tebax 2026 (UTC)
:::Min got ez ê bêjim, li wir hinek zêde valahî xuya dike. Çima gelo? :D -- [[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 17:32, 29 tebax 2026 (UTC)
::::Te bi destê xwe <code><nowiki>#: {{mînak|</nowiki></code> nivîsand? Min çend tişt ceriband eynî xeletî derneket [https://ku.wiktionary.org/w/index.php?title=W%C3%AEk%C3%AEferheng:ceribandin/nim%C3%BBne&diff=prev&oldid=6347017] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 17:39, 29 tebax 2026 (UTC)
:::::Na, min tenê form bikar anî, tenê reqema seyfeyê lê zêde kir, wekî din tiştek nekir. --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 18:14, 29 tebax 2026 (UTC)
::::::Belê niha min dît, min bi rêya AI tiştek hazir kiribû. Li wir #: {{mînak|ku|' heye. Bibore, şaşiya min bû :( --[[Bikarhêner:Şêr|Şêr]] ([[Gotûbêja bikarhêner:Şêr|gotûbêj]]) 18:15, 29 tebax 2026 (UTC)
:::::::Temam baş e ne muhîm e :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez#top|gotûbêj]]) 18:18, 29 tebax 2026 (UTC)
== [[babehoods]] ==
Sorry writting English, [[babehood]] is uncoutable nouns. Therefore, this entry need delete with reason incorrect non-lemma forms. [[User:Hiyuune|<span style="font-family: Segoe UI Light;color:#FF69B4;letter-spacing:">Linh Huynh</span>]] ([[User talk:Hiyuune|<span style="color:#008080;">talk</span>]]) 09:10, 21 îlon 2026 (UTC)
gpp6uegpw4hxb97sa8garce2tit7g1o
teşîrês
0
1230759
6535073
6319160
2026-09-21T11:15:47Z
Kurdistanov
15290
6535073
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=mn}}
# Kesa yan kesê ku bi [[teşî]]yê [[hirî]] yan [[mû]] dirêse.
==== Tewandin ====
{{ku-tewîn-nav/nû|mê û nêr}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
Ji [[teşî]] + [[-rês]] pêk tê.
==== Bide ber ====
* [[teşî]]
==== Jê ====
* [[teşîrêsî]]
==== Werger ====
{{werger-ser}}
{{werger-bin}}
0yq8uzmt9199l1ylhn6tse6mqzwykmr
6535075
6535073
2026-09-21T11:41:09Z
Balyozbot
7867
[[Special:PermanentLink/6298023|Bot]]: rêzkirina beşan
6535075
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=mn}}
# Kesa yan kesê ku bi [[teşî]]yê [[hirî]] yan [[mû]] dirêse.
==== Tewandin ====
{{ku-tewîn-nav/nû|mê û nêr}}
==== Jê ====
* [[teşîrêsî]]
==== Etîmolojî ====
{{bêetîmolojî|ku}}
Ji [[teşî]] + [[-rês]] pêk tê.
==== Bide ber ====
* [[teşî]]
==== Werger ====
{{werger-ser}}
{{werger-bin}}
k953ag0d8wcczsma1gtwqi3tu7l5sha
Wîkîferheng:ceribandin/Default
4
1324891
6534995
6328899
2026-09-20T19:42:58Z
Wikihez
7344
6534995
wikitext
text/x-wiki
{{<noinclude>Wîkîferheng:ceribandin</noinclude>/vê rêzê neguhêre}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Lêker ===
[[Wêne:Example.jpg|thumb|Mînak]]
{{lêker|ku|c=h-gh}}
# Maneya 1ê.
#: {{mk|ku|'''Nimûneyeke''' bikaranînê ya vê peyvê.}}
#* {{bênimûne|ku}}
# {{f|ku|mecazî}} Maneya 2an.
#* {{jêder-pirtûk|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}}
# Maneya 3an.
==== Bikaranîn ====
''Çend not li ser bikaranîna peyvê.''
==== Jê ====
{{kol3|ku|cure=Jê
|ceribandinvan
}}
==== Têkildar ====
* ''Peyvên ku têkiliya [[etîmolojîk]] bi peyvê re hene''.
==== Etîmolojî ====
''Çîroka tarîxî ya eslê kelîmeyê. Nimûneya bandor'': Ji {{deyn|ota|ku|باندره}}, {{q|ota|باكدیره}}, ({{hv|tr|bandıra}}) ji {{ji|vec|ku|bandéra}} ({{hv|it|bandiera}}) ji {{ji|gem|ku}} *''bandwa-'' ({{cotreh|ku|band|bend}}). Bide ber {{hv|fr|bandière}} (wek {{hv|en|banner}}), {{hv|el|παντιέρα}}.
==== Binêre herwiha ====
* ''Peyvên eleqadar lê belê têkiliya etîmolojîk nîne''.
* {{wpedî}}
==== Werger ====
{{werger-ser|1|Maneya 1ê}}
* {{Z|en}}: {{W+|en|word}}
{{werger-bin}}
{{werger-ser|2|Maneya an}}
{{werger-bin}}
{{werger-bnr|Maneya 3an|kelîme}}
=== Navdêr ===
{{ku-tewîn-nav|nav=nimûne|mê}}
{{navdêr|ku|z=m}}
# mane 1
#: mînak 1-1
#: mînak 1-2
#* jêgirtin 1-1
#* jêgirtin 1-2
# mane 2
#: mînak 2-1
#: mînak 2-2
#* jêgirtin 2-1
#* jêgirtin 2-2
# mane 3
==== Werger ====
{{werger-ser|1|Maneya 1ê}}
{{werger-bin}}
{{werger-ser|2|Maneya 2an}}
{{werger-bin}}
{{werger-ser|3|Maneya 3an}}
{{werger-bin}}
=== Çavkanî ===
{{Çavkanî}}
* Çavkaniyên din li vir binivîse.
8017bi829rxd8hpy4lz79p6xi5w7p10
Bikarhêner:Wikihez/Gadget-wikt.add-examples.js
2
1357893
6534964
6531664
2026-09-20T18:41:50Z
Wikihez
7344
DefSideBoxes: strike a quotation queued for removal; a failed hand-over no longer leaves the form disabled
6534964
javascript
text/javascript
// <nowiki>
// dependencies (in MediaWiki:Gadgets-definition): oojs, oojs-ui, ext.gadget.citoid-helper,
// ext.gadget.StorageUtils, ext.gadget.wikt.core, ext.gadget.wikt.edit, ext.gadget.wikt.cite
//
// A ResourceLoader package module: it require()s wikt.cite, so it cannot be loaded as a plain
// script. Load it through its gadget definition or the test loader.
//
// Two copies at once would fight over wikt.addExamples, so the second to arrive does nothing.
window.wikt = window.wikt || {};
wikt.addExamples = wikt.addExamples || {};
var alreadyLoaded = wikt.addExamples.loaded;
wikt.addExamples.loaded = true;
// Formats a string using {0}, {1}, ... placeholders.
if (!String.prototype.format) {
String.prototype.format = function(var_args) {
var args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] !== "undefined" ? args[number] : match;
});
};
}
$(function() {
"use strict";
if (alreadyLoaded) return;
// Entries, reconstructions and sandboxes -- the same set DefSideBoxes and the wizard run on, from
// wikt.core, so a page one of them works on is never a page this one quietly skips.
if (!require("ext.gadget.wikt.core").sections.isEntryPage()) return;
if (mw.config.get("wgPageName") === "Destpêk" || mw.config.get("wgArticleId") === 0) return;
var NAME = "[[Mediawiki:Gadget-wikt.add-examples|Nimûne zêde bike]]";
// This gadget puts nothing on the page of its own. DefSideBoxes opens the form from its own
// "Nimûne/jêgirtin" item, through openFor()/toggleFor() below, and takes the save over.
window.wikt = window.wikt || {};
wikt.addExamples = wikt.addExamples || {};
// Lets another gadget take the save over: "Qeyd bike" becomes msg.add and the edit joins
// that gadget's queue. Pass null to hand publishing back. Handler gets {transform, summary, done, fail}.
wikt.addExamples.setSubmitHandler = function (fn) {
wikt.addExamples.submitHandler = typeof fn === "function" ? fn : null;
};
// Re-clicking the caller's own trigger closes the form again.
wikt.addExamples.toggleFor = function (el) {
var toggle = $(el).data("wiktAddExampleToggle");
if (!toggle) {
return false;
}
toggle();
return true;
};
wikt.addExamples.openFor = function (el) {
var open = $(el).data("wiktAddExampleOpen");
if (!open) {
return false;
}
open();
return true;
};
// Opens the form on one example already under the sense el: itemEl is its <dd>/<li> on the page.
wikt.addExamples.editFor = function (el, itemEl) {
var editItem = $(el).data("wiktAddExampleEdit");
return !!editItem && editItem(itemEl);
};
var VERSION = "2.0";
// Wiki markup for bold, both the button's label and what it inserts.
var BOLD_MARKS = "'''";
// Persists form drafts (localStorage, cookie fallback) so unsaved text survives a failed submit.
var draftStorage = new ObjectStorage("add_examples_draft", 1);
// Citations are built by wikt.cite, the same module the wizard uses; Citoid lives in the
// citoid-helper gadget. "citoid" as a _currentJederKey is a sentinel, not a real jederDefs key.
var cite = require("ext.gadget.wikt.cite");
var core = require("ext.gadget.wikt.core");
var sections = core.sections;
var msg = core.msg;
var edit = require("ext.gadget.wikt.edit");
var jederDefs = cite.jederDefs;
var nlToSpace = cite.nlToSpace;
var boldenPageTitle = cite.boldenPageTitle;
var CATEGORY_TEMPLATE_KEY = cite.CATEGORY_KEY;
var RAW_TEMPLATE_KEY = cite.RAW_KEY;
// Params the form already has boxes for; skipped when building a Kategorî:Çavkanî template's fields.
var CATEGORY_TEMPLATE_COVERED_PARAMS = cite.COVERED_PARAMS;
// A param labeled exactly this is filled from the language dropdown instead of shown as a field,
// and only when the template's own TemplateData declares one.
var CATEGORY_TEMPLATE_LANG_LABEL = cite.LANG_LABEL;
// "span" is the field's width in the 12-column grid the fields render in (see .add-example-jeder-fields
// in the CSS and _setSourceType below) -- field ORDER here is also the grid's reading order.
// Heading/definition-list helpers and the recognized POS heading names, shared with the wizard and
// DefSideBoxes so all three read the same page structure.
var headingOuter = sections.headingOuter;
var languageCodeForOl = sections.languageCodeForOl;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var eachLeafDefinition = sections.eachLeafDefinition;
// A heading's section key, the wizard's unclassified "=== Mane ===" placeholder included: it holds
// real definitions, so examples belong in it, even though it is not a part of speech.
var sectionKeyOf = sections.sectionKeyOf;
// Locating a section and a definition inside it is wikt.edit's job, shared with DefSideBoxes.
var wt = edit.wt;
// Template names that count as real examples, so an annotation template is not mistaken for one.
var exampleTemplateNames = ["jêder"].concat(cite.USEX_NAMES, Object.keys(jederDefs));
// The "no example yet" placeholder, with or without a language parameter.
var EMPTY_EXAMPLE_MARKER = cite.EMPTY_EXAMPLE_MARKER;
// The template name starting a "#:"/"#*" line, or null when the line does not start with one.
function getLineTemplateName(line) {
var m = /^#[:*]\s*\{\{\s*([^|}]+)/.exec(line);
return m ? m[1].trim() : null;
}
// Where a template block starting at startIndex closes its braces, so multi-line calls stay whole.
// The "#" run a definition line carries, so a sense's own annotations can be told from a sub-sense's:
// "#:" belongs to "#", "##:" to "##".
function senseDepth(definitionLine) {
var m = /^#+/.exec(definitionLine || "");
return m ? m[0].length : 1;
}
// Any annotation of that sense -- "#:" an example, "#*" a citation -- at its own depth and no other.
function annotationRegex(definitionLine) {
return new RegExp("^#{" + senseDepth(definitionLine) + "}[:*]");
}
function citationRegex(definitionLine) {
return new RegExp("^#{" + senseDepth(definitionLine) + "}\\*");
}
function findBlockEnd(lines, startIndex) {
var depth = 0;
var i = startIndex;
for (; i < lines.length; i++) {
var opens = (lines[i].match(/\{\{/g) || []).length;
var closes = (lines[i].match(/\}\}/g) || []).length;
depth += opens - closes;
if (depth <= 0) {
break;
}
}
return i;
}
// URL of a template's own wiki page -- lets the user check a Kategorî:Çavkanî search result
// before picking it (see showResults below).
// Escapes str for interpolation into a RegExp: page titles carry regex metacharacters.
var escapeRegExp = edit.util.escapeRegExp;
// The readable half of an mw.Api rejection.
var apiErrorMessage = edit.util.apiErrorMessage;
// Same marker as the wizard's, positioned inside OOUI's own wrapper rather than a new one.
var highlightLinebreak = edit.util.makeLinebreakHighlighter({
prefix: "add-example",
wrap: false,
dataKey: "addExampleLinebreakBackdrop",
title: "Xeta n\u00fb d\u00ea wek <br> b\u00ea niv\u00eesandin"
});
// Linebreak marking on the quote text and its translation/transcription only, not the
// bibliographic fields.
function watchForLinebreaks(widget) {
widget.on("change", function() {
highlightLinebreak(widget.$input[0]);
});
}
// The shared grip from wikt.edit, kept as a sibling inside OOUI's own wrapper and without the saved
// height: these fields are plain MultilineTextInputWidgets with no class to key one by.
function makeManualResizable(textarea) {
edit.util.makeManualResizable(textarea, {
wrap: false,
persist: false,
gripClass: "add-example-resize-grip",
doneKey: "addExampleResizableDone"
});
}
// Applies makeManualResizable to every textarea in `context` (itself or its descendants).
function initManualResize(context) {
var $context = $(context);
var $textareas = $context.is("textarea") ? $context : $context.find("textarea");
$textareas.each(function() {
makeManualResizable(this);
});
}
var api = new mw.Api();
// Kategorî:Çavkanî's templates and their redirects, each redirect its own entry pointing at the
// Language display name, read from the matching section's .besazimani link text.
function languageNameForCode(code) {
var $mark = $(".besazimani[id='" + code + "']").first();
return $mark.length ? $mark.text().trim() : null;
}
// Whether the page title itself (accented Kurdish Latin letters included) is written in Latin script.
var pageTitleIsLatin = !/[^\x00-\x7FÀ-ɏḀ-ỿ‘’]/.test(mw.config.get("wgTitle") || "");
// What an example looks like once rendered: Şablon:jêder's old marker, Modul:usex's containers and
// Modul:quote's wrapper.
var EXAMPLE_SELECTOR = ".example, .benimune, .h-usage-example, .h-quotation, .citation-whole";
// The words drawn for one example: the example or quotation itself, never its translation or source.
function exampleTextOf($item) {
var $own = $item.find(".e-example, .e-quotation").first();
if ($own.length) {
return $own.text();
}
var $italic = $item.children("i").first();
return ($italic.length ? $italic : $item.clone().children("dl, ul, .dsb-example-toolbar").remove().end()).text();
}
// A definition's own citation group, and the last one when it has several: a new citation is
// written after every "#:"/"#*" line already there.
function exampleGroupOf($li) {
return $li.children("ul").filter(function () {
return $(this).children("li").children(EXAMPLE_SELECTOR).length > 0;
}).last();
}
// Every recognized part-of-speech section, down to each of its definitions. Section, language and
// nesting all come from the page's own structure, never from an example that happens to sit there.
// "===" on most entries, "====" where the senses hang off numbered etymologies.
$("h3, h4").each(function() {
var sectionType = sectionKeyOf($(this));
if (!sectionType) {
return; // not a recognized word-sense section (Bilêvkirin, Etîmolojî, Çavkanî, ...)
}
var sectionOrdinal = sectionOrdinalWithinLanguage($(this));
// Forward-walk siblings to the definitions <ol>, skipping over a declension table/TemplateStyles/the
// headword line; stop (nothing to attach to in this section) if another heading comes first instead.
var $ol = null;
for (var $cursor = headingOuter($(this)).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("ol")) {
$ol = $cursor;
break;
}
if (sections.headingDepth($cursor)) {
break;
}
}
if (!$ol) {
return;
}
var language = languageCodeForOl($ol);
if (!language) {
console.warn("Gadget-wikt.add-examples: nikaribe koda zimanê vê beşê bibîne.", $ol);
return;
}
eachLeafDefinition($ol, function($li) {
var definitionLevel = [];
var $definitionItem = $li;
while ($definitionItem.prop("tagName") === "LI") {
// Skip MediaWiki's empty <li class="mw-empty-elt"> so indices match the "#" lines in wikitext.
definitionLevel.splice(0, 0, $definitionItem.prevAll("li:not(.mw-empty-elt)").length);
$definitionItem = $definitionItem.parent().parent();
}
definitionLevel.splice(0, 0, sectionType + "|" + sectionOrdinal);
// Where a submitted example lands: after the group's last example, or after a hidden placeholder
// when the definition has no group.
var $group = exampleGroupOf($li);
var $placeholder = $group.length ? $group.children("li").last()
: $("<li style='display:none'></li>");
var $formItem = $("<li>");
// Never in the DOM: Form toggles it against the title, so it stays as the "is a form built
// for this sense" handle. Nothing on the page opens this gadget -- DefSideBoxes does.
var $button = $("<span>").hide();
// Says which sense and language the open form is for; the row exists only while it is open.
var $title = $("<span class='add-example-form-title'></span>").hide();
$formItem.append($title);
// The group the button lives in: the one on the page, or a new one. A "ul" under a definition
// "li" starts hidden, so a new one needs the explicit display override.
var $ul = $group.length ? $group
: $("<ul>").css("display", "block").append($placeholder);
// A citation already carrying the site's own small edit link does not need a second one.
$ul.find("span.example > span.stubedit").remove();
// The panel opens after $ul, not inside it: full width, clear of the list's hanging indent.
// edit, when given, is {index, text}: the form opens on that example instead of on a new one.
function setFormVisible(visible, edit) {
if (visible) {
// A sense with no example list of its own gets one on first open, not on page load:
// an empty list on every sense of every entry is margin nobody asked for.
// Editing shows its title inside the form, so the list row is only for a new example.
if (!edit && !$ul.parent().length) {
$li.append($ul);
}
if (!edit && !$formItem.parent().length) {
$ul.append($formItem);
}
if (edit) {
$formItem.detach();
}
if (!$button.form) {
$title.text("Nimûneyeke bi " + (languageNameForCode(language) || "zimanekî nenas").toLowerCase() + " lê zêde dikî.");
var form = new Form($placeholder, $button, $title, language, definitionLevel);
form.onHide = function () {
$formItem.detach();
};
$ul.after(form.$element);
}
// Right under the example being edited; after the whole list when adding a new one.
(edit ? $(edit.item) : $ul).after($button.form.$element);
if (edit) {
$button.form.beginEdit(edit.index, edit.text);
} else {
$button.form.endEdit();
$button.form.setVisible(true);
}
} else if ($button.form) {
$button.form.setVisible(false);
$formItem.detach();
}
return false;
}
function openForm() { return setFormVisible(true); }
function toggleForm() { return setFormVisible(!($button.form && $button.form.isVisible())); }
$li.data("wiktAddExampleOpen", openForm);
$li.data("wiktAddExampleToggle", toggleForm);
// Its place among the sense's examples finds the line; the words drawn for it confirm it.
$li.data("wiktAddExampleEdit", function (itemEl) {
var $item = $(itemEl);
var index = sections.exampleItemsOf($li).index($item);
if (index === -1) {
return false;
}
setFormVisible(true, { index: index, text: exampleTextOf($item), item: itemEl });
return true;
});
});
});
// Pulls the form panel out to the page's own edges, undoing the nested list's indent.
function alignFormToPage($form) {
var $container = $form.closest(".mw-parser-output");
if (!$container.length) {
return;
}
// Measure unshifted: this runs on every re-open, and a margin left from last time would otherwise
// count as part of the indent and cancel the alignment out.
$form.css("marginLeft", 0);
var pad = parseFloat(window.getComputedStyle($container[0]).paddingLeft) || 0;
var containerLeft = $container.offset().left + pad;
var indent = $form.offset().left - containerLeft;
$form.css({
marginLeft: indent > 0 ? -indent : 0,
width: $container.width() + "px"
});
}
/**
* language is the section the sense sits in: what the new example is tagged with, and what the
* wikitext lookup searches for. Never the language of an example that happens to be there.
*/
function Form($lastExample, $button, $title, language, definitionLevel) {
var self = this;
this._language = language;
this._definitionLevel = definitionLevel;
this._$lastExample = $lastExample;
this._$button = $button;
this._$button.form = this;
this._$title = $title;
// Where the title lives when adding; while editing it moves into the form itself.
this._$titleHome = $title.parent();
this._currentJederKey = "";
this._jederFieldWidgets = {};
// Set once a template is picked in the Kategorî:Çavkanî mode (see _selectCategoryTemplate); the
// canonical template title (without the "Şablon:" prefix), used as `sablon` in _doSubmit.
this._categoryTemplateName = null;
// Set by _renderCategoryTemplateFields if the picked template's TemplateData has a "koda zimanî" param;
// that param's own key, used to emit "<key>=<language>" in _doSubmit -- null if no such param exists.
this._categoryTemplateLangParamKey = null;
// Set while an example already on the page is being edited (see beginEdit): {index, original,
// example, key}, where key is the source type the line was read into.
this._editing = null;
this._addTitle = null;
// The same prompt the wizard's own example box carries, so the two ask for an example in one
// wording and the language it is to be written in is on screen here too.
this._textInput = new OO.ui.MultilineTextInputWidget({
rows: 4,
placeholder: edit.util.examplePrompt(languageNameForCode(language), false)
});
var textInputLayout = new OO.ui.FieldLayout(this._textInput, {
label: "Metna nimûneyê",
align: "top",
});
// Under the text box: what will happen to the entry's own name on save (see boldenPageTitle), and
// the one markup button worth having beside it. Purely informational, never blocks submitting.
this._titleBoldHint = $("<span class='add-example-title-bold-hint'></span>");
this._updateTitleBoldHint = function() {
var text = self._textInput.getValue().trim();
var title = mw.config.get("wgTitle");
var missing = !!text && !boldenPageTitle(text, title).bolded;
self._titleBoldHint
.toggleClass("add-example-title-bold-warn", missing)
.html(missing
? "Sermade (" + mw.html.escape(title) + ") nehat dîtin; dê neyê <b>stûr</b>kirin."
: "Sermade otomatîk tê <b>stûr</b>kirin.");
// Only where it is needed: when the name is bolded on its own there is nothing to reach for.
self._boldButton.toggle(missing);
};
// Shown only while the name cannot be bolded on its own -- a form the matcher does not recognise,
// or another word the writer wants bolded.
this._boldButton = $("<button type='button' class='add-example-bold-btn'></button>").hide()
.text(BOLD_MARKS).attr("title", "Nivîsa hilbijartî stûr bike");
this._boldButton.on("click", function(e) {
e.preventDefault();
var input = self._textInput.$input[0];
var value = input.value;
var start = input.selectionStart;
var end = input.selectionEnd;
var next, caret;
if (start !== end) {
next = value.slice(0, start) + BOLD_MARKS + value.slice(start, end) + BOLD_MARKS + value.slice(end);
caret = end + BOLD_MARKS.length * 2;
} else {
// Nothing selected: just the marks, where the caret is. Typing carries on after them.
next = value.slice(0, start) + BOLD_MARKS + value.slice(start);
caret = start + BOLD_MARKS.length;
}
self._textInput.setValue(next);
input.focus();
input.setSelectionRange(caret, caret);
});
this._textTools = $("<div class='add-example-text-tools'></div>")
.append(this._boldButton, this._titleBoldHint);
this._updateTitleBoldHint();
// A pasted citation switches to raw mode rather than being wrapped in a second {{nimûne}}: a whole
// line with its marker and a bare template call both count. Only while the type is still citoid.
this._maybeSwitchToRaw = function(value) {
if (self._currentJederKey !== "citoid" ||
!(cite.looksLikeCitationLine(value) || cite.looksLikeTemplate(value))) {
return;
}
var menu = self._sourceTypeDropdown.getMenu();
var item = menu.findItemFromData(RAW_TEMPLATE_KEY);
if (item) {
menu.chooseItem(item); // calls _setSourceType() itself
}
};
this._textInput.on("change", function(value) {
self._applyButton.setDisabled(!value);
self._maybeSwitchToRaw(value);
self._updateReadyHint();
self._updateTitleBoldHint();
});
watchForLinebreaks(this._textInput);
// Source type: free text, a jêder-* template, or "citoid" -- a sentinel meaning "not yet detected",
// switched automatically once a Citoid fetch succeeds.
var sourceTypeMenuItems = [
new OO.ui.MenuOptionWidget({ data: "citoid", label: msg.citoidAuto }),
new OO.ui.MenuOptionWidget({ data: "", label: msg.sourceByHand }),
];
for (var jederKey in jederDefs) {
if (jederDefs.hasOwnProperty(jederKey)) {
sourceTypeMenuItems.push(new OO.ui.MenuOptionWidget({
data: jederKey,
label: jederDefs[jederKey].label,
}));
}
}
sourceTypeMenuItems.push(new OO.ui.MenuOptionWidget({
data: CATEGORY_TEMPLATE_KEY,
label: msg.sourceFromCategory,
}));
// Paste a finished citation into the example box and have it saved exactly as typed -- the quickest
// way to reuse one jêgirtin across several entries.
sourceTypeMenuItems.push(new OO.ui.MenuOptionWidget({
data: RAW_TEMPLATE_KEY,
label: msg.sourceRaw,
}));
// Shown only in raw mode, where there is nothing else to fill in.
this._rawTemplateHint = new OO.ui.MessageWidget({
type: "notice",
inline: true,
label: "Şablona temam li qada \"Metna nimûneyê\" binivîse \u2014 wek xwe tê qeydkirin. "
+ "Tenê navê maddeyê yê di nav jêgirtin= de tê stûrkirin.",
classes: ["add-example-raw-hint"],
});
this._rawTemplateHint.toggle(false);
this._sourceTypeDropdown = new OO.ui.DropdownWidget({
menu: { items: sourceTypeMenuItems },
label: msg.citoidAuto,
});
var sourceTypeLayout = new OO.ui.FieldLayout(this._sourceTypeDropdown, {
label: "Şablona çavkaniyê",
align: "top",
});
this._sourceTypeDropdown.getMenu().on("choose", function(menuOption) {
self._setSourceType(menuOption.getData());
});
this._sourceInput = new OO.ui.MultilineTextInputWidget();
this._sourceInput.on("change", function() {
self._updateReadyHint();
});
this._sourceInputLayout = new OO.ui.FieldLayout(this._sourceInput, {
label: "Çavkaniya nimûneyê",
align: "top",
});
// Visible in every mode: submit() reads it as "url=" regardless. The Citoid button sits beside it.
this._sourceURLInput = new OO.ui.TextInputWidget({
icon: "link",
placeholder: "https://… an jî DOI/ISBN",
});
this._citoidButton = new OO.ui.ButtonWidget({
label: msg.citoidFill,
icon: "download",
flags: ["progressive"],
title: "Li gorî URL, DOI an ISBNya jorîn, malûmatan ji Citoid tîne û qadên şablona jêder-* otomatîk tijî dike.",
classes: ["add-example-citoid-fetch"],
});
this._citoidButton.on("click", this._fillFromCitoid.bind(this));
this._sourceURLInput.on("change", function() {
self._updateReadyHint();
self._autofillVideoTimestamp();
});
// URL input and its auto-fill button as one flex row; a bare OO.ui.Widget used as the container.
this._urlRowWidget = new OO.ui.Widget({ classes: ["add-example-url-row"] });
this._urlRowWidget.$element.append(this._sourceURLInput.$element, this._citoidButton.$element);
this._sourceURLInputLayout = new OO.ui.FieldLayout(this._urlRowWidget, {
label: "Lînk, DOI an ISBNya çavkaniyê",
align: "top",
help: "Li qutiyê URL, DOI an ISBN binivîse û pê lê bike butona '" + msg.citoidFill + "' da ku otomatîk çavkanî were dagirtin.",
helpInline: true,
});
// Same wording as above, so the initial _setSourceType("citoid") rebuilds nothing.
this._urlFieldIsFreeTextWording = false;
// Shown right after a successful Citoid fetch (see _fillFromCitoid); hidden again on clear.
this._citoidSuccessMsg = $("<span class='add-example-citoid-success'></span>").text(msg.citoidFilled).hide();
// Clears the URL field and any filled jêder-* fields without touching the example text/translation.
this._citoidClearButton = new OO.ui.ButtonWidget({
label: "Vala bike",
icon: "trash",
framed: false,
title: "Lînkê û qadên jêder-* yên vê beşê vala bike",
classes: ["add-example-citoid-clear"],
});
this._citoidClearButton.on("click", function() {
self._sourceURLInput.setValue("");
self._citoidSuccessMsg.hide();
Object.keys(self._jederFieldWidgets).forEach(function(key) {
self._jederFieldWidgets[key].setValue("");
});
});
// Plain div (not OO.ui.FieldsetLayout) so the grid CSS class applies directly to the field items.
this._jederFieldsFieldset = $("<div class='add-example-jeder-fields'>");
// Header and clear button above the fields grid. Only meaningful once a real jêder-* template is
// active, so the whole section toggles together in _setSourceType().
this._jederSection = $("<div class='add-example-jeder-section'>").append(
$("<div class='add-example-jeder-header'>")
.append($("<span class='add-example-jeder-header-label'>Malûmatên çavkaniyê</span>"))
.append(this._citoidClearButton.$element),
this._jederFieldsFieldset
);
// wikt.cite's picker, the one the wizard and DefSideBoxes show. Sits under the source-type
// dropdown, not in the fields section, and is built once so switching modes keeps its handlers.
var categoryPicker = cite.buildCategoryTemplateSearch({
describe: true,
onPick: function (entry) {
self._selectCategoryTemplate(entry);
}
});
this._categoryTemplateInput = categoryPicker.$input;
this._categoryTemplateStatus = categoryPicker.$status;
this._categoryTemplateSearchRow = categoryPicker.$element
.addClass("add-example-cat-tpl-search-row").hide();
this._translationInput = new OO.ui.MultilineTextInputWidget({ rows: 4 });
var translationInputLayout = new OO.ui.FieldLayout(this._translationInput, {
// A Kurmancî example gets an explanation rather than a translation.
label: language === "ku" ? "Îzaheke kurmancî ya nimûneyê" : "Tercimeya kurmancî ya nimûneyê",
align: "top",
});
watchForLinebreaks(this._translationInput);
this._transcriptionInput = new OO.ui.MultilineTextInputWidget({ rows: 4 });
var transcriptionInputLayout = new OO.ui.FieldLayout(this._transcriptionInput, {
label: "Translîterasyona nimûneyê",
align: "top",
help: "Tenê lê zêde bike eger metna di nimûneyê de bi alfabeya latînî nehatibe nivîsandin.",
helpInline: true,
});
watchForLinebreaks(this._transcriptionInput);
this._applyButton = new OO.ui.ButtonWidget({
label: "Qeyd bike",
icon: "check",
title: "Nimûneyê ji bo vê tarîfê nimûneyê lê zêde bike",
flags: ["progressive", "primary"],
disabled: true,
});
this._applyButton.on("click", this.submit.bind(this));
// Reactive "ready to submit" hint next to Qeyd bike: no text yet, text but no source (still submittable
// as a bare {{nimûne}}), or fully ready -- kept in sync by _updateReadyHint(), wired to every field below.
this._readyHint = $("<span class='add-example-ready-hint'></span>").text("Metna nimûneyê lazim e.");
// True while a Citoid-eligible URL is typed but unfetched and no jêder field has been filled by
// hand -- the "looks done but isn't" case.
this._citoidPending = function() {
return self._currentJederKey !== "" &&
self._currentJederKey !== CATEGORY_TEMPLATE_KEY &&
self._currentJederKey !== RAW_TEMPLATE_KEY &&
!!self._sourceURLInput.getValue().trim() &&
!self._citoidSuccessMsg.is(":visible") &&
!Object.keys(self._jederFieldWidgets).some(function(key) {
return self._jederFieldWidgets[key].getValue().trim();
});
};
// A Kurmancî jêgirtin takes no translation or explanation, only a {{nimûne}} does. An edited line
// that already has one keeps its box.
this._syncTranslationBox = function() {
if (self._language !== "ku") {
return;
}
var key = self._currentJederKey;
var isQuote = key === RAW_TEMPLATE_KEY || key === CATEGORY_TEMPLATE_KEY || !!jederDefs[key] ||
!!self._sourceInput.getValue().trim() || !!self._sourceURLInput.getValue().trim();
translationInputLayout.toggle(!isQuote ||
!!(self._editing && self._translationInput.getValue().trim()));
};
this._updateReadyHint = function() {
self._syncTranslationBox();
if (!self._textInput.getValue()) {
self._readyHint.text("Metna nimûneyê lazim e.");
return;
}
if (self._citoidPending()) {
self._readyHint.text("Pêşî '" + msg.citoidFill + "' bitikîne.");
return;
}
// Raw mode carries its own source inside the pasted template, so there is nothing else to ask for.
if (self._currentJederKey === RAW_TEMPLATE_KEY) {
self._readyHint.text("Hazir e.");
return;
}
var hasSource = self._sourceInput.getValue().trim() || self._sourceURLInput.getValue().trim() ||
Object.keys(self._jederFieldWidgets).some(function(key) {
return self._jederFieldWidgets[key].getValue().trim();
});
self._readyHint.text(hasSource ? "Hazir e." : "Çavkanî nehatiye dayîn.");
};
// Prefills Dem from the URL's own "t=" once jêder-vîdeo is active; never overwrites typed text.
this._autofillVideoTimestamp = function() {
var demWidget = self._jederFieldWidgets.dem;
if (self._currentJederKey !== "jêder-vîdeo" || !demWidget || demWidget.getValue().trim()) {
return;
}
var dem = wikt.citoid.extractVideoTimestamp(self._sourceURLInput.getValue().trim());
if (dem && !demWidget.getValue().trim()) {
demWidget.setValue(dem);
}
};
// framed:false (+ the "destructive" flag for the red text/hover) matches the mockup's borderless
// "Betal bike" -- a plain colored link rather than a boxed button, distinguishing it from Paqij bike.
this._cancelButton = new OO.ui.ButtonWidget({
label: "Betal bike",
icon: "close",
title: "Formê bigire",
flags: ["destructive"],
framed: false,
});
this._cancelButton.on("click", function() {
self.setVisible(false);
});
this._clearButton = new OO.ui.ButtonWidget({
label: "Paqij bike",
icon: "trash",
title: "Hemû qadan vala bike û pêşnûyisên hilanîbûyî jê bibe",
});
this._clearButton.on("click", function() {
self.clear();
self._clearDraft();
});
// Only while an example on the page is being edited.
this._deleteButton = new OO.ui.ButtonWidget({
label: "Jê bibe",
icon: "trash",
flags: ["destructive"],
});
this._deleteButton.toggle(false);
this._deleteButton.on("click", function() {
OO.ui.confirm("Ev nimûne dê were rakirin. Dewam bike?").done(function(ok) {
if (ok) {
self._submitDelete();
}
});
});
// A CSS spinner rather than a Commons GIF: no request, and it follows dark/light mode.
this._loadingImage = new OO.ui.Widget({
classes: ["add-example-loading"],
});
// The ring alone says nothing to a reader who has asked for less motion: MediaWiki's own
// reduced-motion reset cuts every animation to 0.01ms, so it never turns for them. The words
// beside it carry the state either way.
this._loadingImage.$element.append(
$('<span class="add-example-spinner" aria-hidden="true"></span>'),
$('<span class="add-example-loading-label"></span>').text(msg.loading)
);
this._loadingImage.toggle(false);
// Two-column top: example text (with translation/transliteration for non-ku) on the left, source
// type and link on the right. Plain divs, so the CSS can lay them out as a grid.
// The translation box is there for Kurmancî too: {{nimûne}} takes one for every language.
var $topLeft = $("<div class='add-example-top-col'>").append(textInputLayout.$element, this._textTools,
translationInputLayout.$element);
if (!pageTitleIsLatin) {
$topLeft.append(transcriptionInputLayout.$element);
}
// Kept for _fill, which adds either box when an edited line already carries one.
this._$topLeft = $topLeft;
this._translationInputLayout = translationInputLayout;
this._transcriptionInputLayout = transcriptionInputLayout;
var $topRight = $("<div class='add-example-top-col'>").append(
sourceTypeLayout.$element,
this._categoryTemplateSearchRow,
this._rawTemplateHint.$element,
this._sourceInputLayout.$element,
this._sourceURLInputLayout.$element,
this._citoidSuccessMsg
);
var $topGrid = $("<div class='add-example-top-grid'>").append($topLeft, $topRight);
// Saved on the instance (not just a local var) so _setSourceType() can swap the URL field's label/help
// wording in and out of it when switching to/from free text (see the FieldLayout replaceWith() there).
this._fieldsLayout = $("<div class='add-example-fieldset'>").append($topGrid, this._jederSection);
// Bottom action row: Qeyd bike (+ spinner and hint), a spacer, then Paqij bike and Betal bike. A
// flex div rather than an OOUI layout, so the hint and spacer can sit among the widgets.
var $buttonsRow = $("<div class='add-example-main-buttons'>").append(
this._applyButton.$element,
this._loadingImage.$element,
this._readyHint,
$("<span class='add-example-buttons-spacer'></span>"),
this._deleteButton.$element,
this._clearButton.$element,
this._cancelButton.$element
);
this._frame = new OO.ui.PanelLayout({
id: "add-example-definition-{0}-form".format(this._definitionLevel.join("-")),
classes: ["add-example-form"],
expanded: false,
});
this._frame.$element.append(this._fieldsLayout, $buttonsRow);
// selectItemByData() does not fire "choose", so the initial mode is set up by hand.
this._sourceTypeDropdown.getMenu().selectItemByData("citoid");
this._setSourceType("citoid");
initManualResize(this._frame.$element);
}
Form.prototype = {
// Returns the jQuery object for this form.
get $element() {
return this._frame.$element;
},
// Switches the source section between free text, Citoid auto-detect and a chosen jêder-* template,
// rebuilding the field list. The URL field stays visible in every mode.
_setSourceType: function(jederKey) {
var self = this;
this._currentJederKey = jederKey;
this._jederFieldsFieldset.empty();
this._jederFieldWidgets = {};
this._categoryTemplateName = null;
this._categoryTemplateLangParamKey = null;
var isRealJederKey = jederKey && jederDefs[jederKey];
var isCategoryTemplateKey = jederKey === CATEGORY_TEMPLATE_KEY;
var isRawTemplate = jederKey === RAW_TEMPLATE_KEY;
if (isRealJederKey) {
var def = jederDefs[jederKey];
def.fields.forEach(function(field) {
var widget = field.multiline
? new OO.ui.MultilineTextInputWidget({ rows: 2, autosize: false })
: new OO.ui.TextInputWidget();
self._jederFieldWidgets[field.key] = widget;
widget.on("change", function(value) {
// sal and tarîx say the same thing twice and Modul:quote refuses both; filling one
// clears the other rather than letting the citation break on save.
var partner = value && cite.exclusivePartner(field.key);
if (partner && self._jederFieldWidgets[partner]) {
self._jederFieldWidgets[partner].setValue("");
}
self._updateReadyHint();
});
var layout = new OO.ui.FieldLayout(widget, {
label: field.label,
align: "top",
classes: ["add-example-field-span-" + (field.span || 4)],
});
self._jederFieldsFieldset.append(layout.$element);
});
}
// The box lives in $topRight always; here it is only shown for this mode, and reset on the way out
// so a stale query does not linger.
this._categoryTemplateSearchRow.toggle(isCategoryTemplateKey);
// Raw mode has no source fields of its own: the citation is already written out in full.
this._rawTemplateHint.toggle(isRawTemplate);
if (!isCategoryTemplateKey) {
this._categoryTemplateInput.val("");
}
// The fields grid is for a real jêder-* template. The Kategorî:Çavkanî picker shows it only once a
// template is picked (see _selectCategoryTemplate).
this._jederSection.toggle(!!isRealJederKey);
// Free text textarea only for "" (free text); hidden for "citoid" and any real jêder-* key.
this._sourceInputLayout.toggle(jederKey === "");
// No Citoid button for free text (nothing to fill) or the Kategorî:Çavkanî picker (no mapping for
// an arbitrary template); shown for auto-detect and any jêder-* key.
this._citoidButton.toggle(jederKey !== "" && !isCategoryTemplateKey && !isRawTemplate);
// Plain "attach a link" wording for free text, Citoid wording otherwise. Rebuilt only when crossing
// that boundary.
var isFreeText = jederKey === "";
if (this._urlFieldIsFreeTextWording !== isFreeText) {
this._urlFieldIsFreeTextWording = isFreeText;
// On the <input> itself: TextInputWidget has no setter for its placeholder.
this._sourceURLInput.$input.attr("placeholder", isFreeText ? "URLyeke wekî https://…" : "URLyek wekî https://… an jî DOI/ISBN");
var $oldLayout = this._sourceURLInputLayout.$element;
// Re-wraps the SAME _urlRowWidget (still holding _sourceURLInput + _citoidButton) in a new
// FieldLayout with the new label/help; the widget's $element moves along with it.
this._sourceURLInputLayout = new OO.ui.FieldLayout(this._urlRowWidget, isFreeText ? {
label: "Adresa webê ya nimûneyê",
align: "top",
help: "Tenê binivîse eger lînk jixwe di referansa çavkaniyê de tinebe.",
helpInline: true,
} : {
label: "Lînk, DOI an ISBNya çavkaniyê",
align: "top",
help: "Ev dibe lînka çavkaniyê (url=). Ji bo şablonên jêder-*, dikare bi Citoid an bi destan jî bên dagirtin.",
helpInline: true,
});
$oldLayout.replaceWith(this._sourceURLInputLayout.$element);
}
// The URL/Citoid row is normally always visible (see the constructor comment), but Citoid has no
// mapping for an arbitrary Kategorî:Çavkanî template, so it'd just be dead UI in that one mode.
this._sourceURLInputLayout.toggle(!isCategoryTemplateKey && !isRawTemplate);
this._autofillVideoTimestamp();
this._updateReadyHint();
initManualResize(this._jederFieldsFieldset); // covers a freshly-rebuilt "sernav" field (jêder-*); the Kategorî:Çavkanî picker's own fields are resized separately once picked, see _renderCategoryTemplateFields
},
// Wires the search box's handlers and starts the template fetch. Called once: the box lives in
// $topRight and is only shown or hidden by _setSourceType.
// Records the picked template's canonical name -- a redirect resolves to its target -- and shows
// its parameter fields.
_selectCategoryTemplate: function(entry) {
// Left as the plain label (not "label → target") -- putting the arrow in the input's own value
// would get re-used as the search query if the box is focused again to pick something else.
this._categoryTemplateInput.val(entry.label);
this._categoryTemplateName = entry.target;
this._jederSection.toggle(true);
return this._renderCategoryTemplateFields(entry.target);
},
// One text input per TemplateData param of `name`, into the shared "Malûmatên çavkaniyê" grid, or a
// lone "Rûpel" box when the template has no TemplateData.
_renderCategoryTemplateFields: function(name) {
var self = this;
this._jederFieldsFieldset.empty();
this._jederFieldWidgets = {};
this._categoryTemplateLangParamKey = null;
return cite.templateParams(name).then(function(params) {
// A param labeled "koda zimanî" is filled from the language dropdown: found before filtering, so
// it is left out of the fields and emitted by _doSubmit.
(params || []).forEach(function(p) {
if ((p.label || "").trim().toLowerCase() === CATEGORY_TEMPLATE_LANG_LABEL) {
self._categoryTemplateLangParamKey = p.key;
}
});
var filtered = (params || []).filter(function(p) {
return CATEGORY_TEMPLATE_COVERED_PARAMS.indexOf(p.key) === -1 && p.key !== self._categoryTemplateLangParamKey;
});
var list = filtered.length ? filtered : [{ key: "rûpel", label: "Rûpel", required: false }];
list.forEach(function(p) {
// Reuse the jêder-* field's own width and multiline when the key matches one (cite.fieldSize).
var sizeHint = cite.fieldSize(p.key);
var widget = (sizeHint && sizeHint.multiline)
? new OO.ui.MultilineTextInputWidget({ rows: 2, autosize: false })
: new OO.ui.TextInputWidget();
self._jederFieldWidgets[p.key] = widget;
widget.on("change", function() {
self._updateReadyHint();
});
var layout = new OO.ui.FieldLayout(widget, {
label: p.label,
align: "top",
classes: ["add-example-field-span-" + (sizeHint ? sizeHint.span : 4)],
});
self._jederFieldsFieldset.append(layout.$element);
});
self._updateReadyHint();
initManualResize(self._jederFieldsFieldset); // matters now that a matched key like "sernav" can render multiline
});
},
// Fetches data via wikt.citoid, picks the matching jêder-* template, and fills its empty fields.
_fillFromCitoid: function() {
var self = this;
var query = this._sourceURLInput.getValue().trim();
if (!query) {
mw.notify(msg.citoidNeedsUrl);
return;
}
this._citoidButton.setDisabled(true);
this._loadingImage.toggle(true);
cite.fetchFromCitoid(query).then(function(result) {
// Citoid may pick the type only while none is chosen; a hand-picked one is kept.
var wasAutoDetect = self._currentJederKey === "citoid";
if (wasAutoDetect) {
var menu = self._sourceTypeDropdown.getMenu();
var item = menu.findItemFromData(result.jederKey);
if (item) {
menu.chooseItem(item); // this calls _setSourceType(jederKey) automatically
}
}
var targetJederKey = self._currentJederKey;
// Citoid shaped its fields for the type it detected; re-derive them when the active template
// differs.
var fields = targetJederKey === result.jederKey ? result.fields
: cite.citoidFieldsFor(targetJederKey, result.raw, query);
Object.keys(fields).forEach(function(key) {
var widget = self._jederFieldWidgets[key];
if (widget && !widget.getValue()) {
widget.setValue(fields[key]);
}
});
self._citoidSuccessMsg.show();
self._updateReadyHint();
mw.notify("Malûmat ji Citoid hatin barkirin.");
}, function(errorMsg) {
// Citoid cannot fetch some sites (x.com), so fall back to jêder-websîte with a guessed name.
var fallbackSite = wikt.citoid.websiteNameForUrl(query);
if (fallbackSite) {
if (self._currentJederKey !== "jêder-websîte") {
var menu = self._sourceTypeDropdown.getMenu();
var item = menu.findItemFromData("jêder-websîte");
if (item) {
menu.chooseItem(item); // this calls _setSourceType("jêder-websîte") automatically
}
}
var websiteWidget = self._jederFieldWidgets["websîte"];
if (websiteWidget && !websiteWidget.getValue()) {
websiteWidget.setValue(fallbackSite);
}
mw.notify("Citoid nikare vê malperê bixwîne; şablona Websîte hate hilbijartin, ji kerema xwe mayî bi destan tijî bike.");
return;
}
mw.notify(typeof errorMsg === "string" ? errorMsg : "Nikare malûmatan ji Citoid bistîne. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function() {
self._citoidButton.setDisabled(false);
self._loadingImage.toggle(false);
});
},
// Shows or hides the form and restores any saved draft. The title takes the button's place while
// open; the button stays put for when it closes.
isVisible: function() {
return this._frame.isVisible();
},
setVisible: function(visible) {
this._$button.toggle(!visible);
this._$title.toggle(visible);
this._frame.toggle(visible);
// Closing from the form's own "Betal bike" has to take the row away too, or the empty <li>
// it lives in is left behind as a bullet with nothing after it.
if (!visible && typeof this.onHide === "function") {
this.onHide();
}
if (visible) {
// Says what the button will actually do: publish now, or join whoever has taken the save
// over. Set here rather than at build time, since the owner may install itself later.
this._applyButton.setLabel(wikt.addExamples.submitHandler ? msg.add : "Qeyd bike");
alignFormToPage(this._frame.$element);
// A draft belongs to a new example; an edited one opens with what its line says.
var draft = this._editing ? {} : (draftStorage.Get() || {});
if (!this._textInput.getValue() && draft.text) {
this._textInput.setValue(draft.text);
}
if (!this._sourceInput.getValue() && draft.source) {
this._sourceInput.setValue(draft.source);
}
if (!this._sourceURLInput.getValue() && draft.sourceURL) {
this._sourceURLInput.setValue(draft.sourceURL);
}
if (!this._translationInput.getValue() && draft.translation) {
this._translationInput.setValue(draft.translation);
}
if (!this._transcriptionInput.getValue() && draft.transcription) {
this._transcriptionInput.setValue(draft.transcription);
}
}
},
// Opens the form on the index-th example of this sense. drawnText is what the page shows for it: the
// line found at that place has to say the same before anything from it is put in the form.
beginEdit: function(index, drawnText) {
var self = this;
this.clear();
this._editing = { index: index, original: null, example: null, key: null };
if (this._addTitle === null) {
this._addTitle = this._$title.text();
}
this._$title.text("Vê nimûneyê diguherînî.");
this._frame.$element.prepend(this._$title);
this._deleteButton.toggle(true);
this.setVisible(true);
this._applyButton.setDisabled(true);
this._deleteButton.setDisabled(true);
this._loadingImage.toggle(true);
var sectionKeyParts = this._definitionLevel[0].split("|");
api.get({
action: "query",
prop: "revisions",
titles: mw.config.get("wgPageName"),
rvprop: "content",
rvslots: "main",
formatversion: 2
}).then(function(data) {
var editing = self._editing;
if (!editing || editing.index !== index || editing.original !== null) {
return; // closed, or opened on another example, while this was loading
}
var content = data.query.pages[0].revisions[0].slots.main.content;
// The page as it will read once the queued changes are saved, so an example already edited
// in the queue is found as it is drawn.
if (typeof wikt.addExamples.pendingContent === "function") {
content = wikt.addExamples.pendingContent(content);
}
var loc = wt.locateSection(content, self._language, sectionKeyParts[0], parseInt(sectionKeyParts[1], 10));
var def = wt.findDefinitionLine(loc.lines, loc.posLineIndex, self._definitionLevel);
var block = cite.exampleBlocks(loc.lines, def).filter(function(b) {
return b.example;
})[index];
if (!block || !cite.exampleMatches(drawnText, block.example)) {
throw new edit.util.SubmitAbort("Ev nimûne di wîkîtextê de nehat dîtin — rûpelê nû bike û dîsa biceribîne.");
}
editing.original = block.text;
editing.example = block.example;
return self._fill(block.example).then(function(aliasMap) {
editing.aliases = aliasMap;
editing.key = self._currentJederKey;
self._syncTranslationBox();
self._deleteButton.setDisabled(false);
self._loadingImage.toggle(false);
});
}).catch(function(err) {
self._loadingImage.toggle(false);
mw.notify(err instanceof edit.util.SubmitAbort ? err.message : "Nikare muhtewaya heyî bixwîne.", { type: "error" });
self.setVisible(false);
self.endEdit();
});
},
// Leaves edit mode: the next time the form opens, it adds a new example again.
endEdit: function() {
if (!this._editing) {
return;
}
this._editing = null;
this._deleteButton.toggle(false);
if (this._addTitle !== null) {
this._$title.text(this._addTitle);
}
this._$titleHome.append(this._$title);
this.clear();
},
// Puts a line read by cite.parseCitationLine() into the form. A plain italic line and every
// {{nimûne}} redirect read as {{nimûne}}, so saving writes them as that. Resolves once the fields
// that depend on the template's own parameters -- a Kategorî:Çavkanî template's boxes, an alias
// such as "r=" for "rûpel=" -- are drawn and filled.
_fill: function(example) {
var self = this;
var key = example.mode === "jeder" ? example.template
: example.mode === "category" ? CATEGORY_TEMPLATE_KEY
: example.mode === "raw" ? RAW_TEMPLATE_KEY
: "";
this._sourceTypeDropdown.getMenu().selectItemByData(key);
this._setSourceType(key);
this._sourceInput.setValue(example.source);
this._sourceURLInput.setValue(example.sourceURL);
this._transcriptionInput.setValue(example.transcription);
var drawn = example.mode === "category"
? this._selectCategoryTemplate({ label: example.template, target: example.template })
: $.Deferred().resolve().promise();
var aliasTemplate = example.mode === "jeder" || example.mode === "category" ? example.template
: example.mode === "nimune" ? "nimûne"
: example.mode === "source" ? "jêder"
: null;
var aliases = aliasTemplate ? cite.templateAliases(aliasTemplate) : $.Deferred().resolve({}).promise();
return $.when(drawn, aliases).then(function(_, aliasMap) {
cite.applyAliases(example, aliasMap || {}, Object.keys(self._jederFieldWidgets));
// buildCitation() writes the language parameter itself.
var langKey = self._categoryTemplateLangParamKey;
if (langKey) {
example.extra = example.extra.filter(function(param) {
return $.trim(param.split("=")[0]) !== langKey;
});
}
self._textInput.setValue(example.text);
self._translationInput.setValue(example.translation);
Object.keys(example.fields).forEach(function(field) {
self._jederFieldWidgets[field].setValue(example.fields[field]);
});
// A box the form leaves out for this language still opens when the line already has something in it.
[[example.translation, self._translationInputLayout], [example.transcription, self._transcriptionInputLayout]]
.forEach(function(pair) {
if (pair[0] && !$.contains(self._$topLeft[0], pair[1].$element[0])) {
self._$topLeft.append(pair[1].$element);
}
});
// Kept for the save, which puts the rebuilt line's parameters back in the page's order.
return aliasMap || {};
});
},
// Removes the example being edited, through the same hand-over as a save.
_submitDelete: function() {
var self = this;
function settle() {
self._deleteButton.setDisabled(false);
self._applyButton.setDisabled(!self._textInput.getValue());
}
this._applyButton.setDisabled(true);
this._deleteButton.setDisabled(true);
this._sendReplacement(null, "−nimûneya bikaranînê bi amûra « {0} » (v{1}).".format(NAME, VERSION), settle, function(message) {
mw.notify(message || msg.saveFailed, { type: "error" });
settle();
});
},
// Replaces the example being edited with code, or removes it when code is null: queued with
// DefSideBoxes when it has taken the save over, published at once otherwise.
_sendReplacement: function(code, summary, reenable, error) {
var self = this;
var editing = this._editing;
var parts = this._definitionLevel[0].split("|");
var language = this._language;
var definitionLevel = this._definitionLevel;
function transform(revision) {
return {
text: cite.replaceExample(revision.content, language, parts[0], parseInt(parts[1], 10),
definitionLevel, editing.index, editing.original, code),
summary: summary
};
}
var handOver = wikt.addExamples.submitHandler;
if (typeof handOver === "function") {
try {
handOver({
transform: transform,
summary: summary,
removes: code === null,
// No key of its own: an example edited twice is two entries, applied in turn, the
// second reading the line as the first left it.
done: function() {
self.setVisible(false);
self.endEdit();
reenable();
},
fail: function(message) {
error(message || "Nikare li rêza guhartinan zêde bike.");
}
});
} catch (e) {
error("Nikare li rêza guhartinan zêde bike: " + (e && e.message ? e.message : e));
}
return;
}
api.edit(mw.config.get("wgPageName"), transform).then(function() {
location.reload();
}, function(codeOrAbort, result) {
error(codeOrAbort instanceof edit.util.SubmitAbort ? codeOrAbort.message
: msg.saveFailed + apiErrorMessage(codeOrAbort, result));
});
},
// Drops the saved draft so it cannot resurface on another page.
_clearDraft: function() {
draftStorage.Set({});
},
// Clears all fields in this form.
clear: function() {
this._textInput.setValue("");
this._sourceInput.setValue("");
this._sourceURLInput.setValue("");
this._translationInput.setValue("");
this._transcriptionInput.setValue("");
this._citoidSuccessMsg.hide();
this._sourceTypeDropdown.getMenu().selectItemByData("citoid");
this._setSourceType("citoid");
},
// Warns when a Citoid URL was typed but never fetched, then defers to _doSubmit. A name that
// cannot be bolded is reported under the text box instead, never blocking.
submit: function() {
if (this._citoidPending() && !window.confirm("Te lînk, DOI an ISBN nivîsand lê hîn jê malûmat nehatine standin. Bêyî wan berdewam bikî?")) {
return;
}
this._doSubmit();
},
// Generates and submits the wikicode, then inserts the resulting HTML element if no errors occurred.
_doSubmit: function() {
var self = this;
self._textInput.setDisabled(true);
self._sourceInput.setDisabled(true);
self._sourceURLInput.setDisabled(true);
self._translationInput.setDisabled(true);
self._transcriptionInput.setDisabled(true);
self._sourceTypeDropdown.setDisabled(true);
Object.keys(self._jederFieldWidgets).forEach(function(key) {
self._jederFieldWidgets[key].setDisabled(true);
});
// self._disableTranslationChk.setDisabled(true);
this._applyButton.setDisabled(true);
this._clearButton.setDisabled(true);
this._loadingImage.toggle(true);
var source = this._sourceInput.getValue().trim();
var sourceURL = this._sourceURLInput.getValue().trim();
var text = this._textInput.getValue().trim();
// The filled-in fields of the selected template. buildCitation() adds the language parameter and
// the "Ç:" flag itself.
var jederFields = {};
var usingCategoryTemplate = this._currentJederKey === CATEGORY_TEMPLATE_KEY && !!this._categoryTemplateName;
if (usingCategoryTemplate) {
Object.keys(self._jederFieldWidgets).forEach(function(key) {
// A raw linebreak would break the single-line "#*" wikitext just like in the example text,
// but these are plain param values (not display text), so collapse to a space, not <br>.
var value = nlToSpace(self._jederFieldWidgets[key].getValue().trim());
if (value) {
jederFields[key] = value;
}
});
} else if (this._currentJederKey && jederDefs[this._currentJederKey]) {
jederDefs[this._currentJederKey].fields.forEach(function(field) {
var widget = self._jederFieldWidgets[field.key];
var value = widget ? nlToSpace(widget.getValue().trim()) : "";
if (value) {
jederFields[field.key] = value;
}
});
cite.withLinkAsUrl(this._currentJederKey, jederFields, sourceURL);
}
// A Kategorî:Çavkanî template is used as soon as it's picked, even with every field left blank
// (still submittable as a bare jêgirtin=); a jêder-* type only counts once a field is actually filled.
var usingJederTemplate = usingCategoryTemplate || (this._currentJederKey && Object.keys(jederFields).length > 0);
var usingRawTemplate = this._currentJederKey === RAW_TEMPLATE_KEY;
var minakAnNa, sablon;
if (usingRawTemplate) {
// A citation, so "#*" like the others -- the line itself is built below, not assembled here.
minakAnNa = "*";
sablon = "";
} else if (usingJederTemplate) {
// The jêder-*/Kategorî:Çavkanî template itself is used as the template, not as the "2=" content.
minakAnNa = "*";
sablon = usingCategoryTemplate ? this._categoryTemplateName : this._currentJederKey;
} else if (source || sourceURL) {
minakAnNa = "*";
sablon = "jêder";
} else {
minakAnNa = ":";
sablon = "nimûne";
}
var listMarker = "".padStart(this._definitionLevel.length - 1, "#") + minakAnNa;
var title = mw.config.get('wgTitle');
// One builder for every citation line here and in the wizard. Bolding the entry name, the language
// parameter, the "Ç:" flag and <br> conversion all belong to it.
var code = cite.buildCitation({
mode: usingRawTemplate ? "raw"
: usingCategoryTemplate ? "category"
: usingJederTemplate ? "jeder"
: "plain",
marker: listMarker,
language: this._language,
title: title,
template: usingCategoryTemplate ? this._categoryTemplateName : this._currentJederKey,
fields: jederFields,
langParamKey: this._categoryTemplateLangParamKey,
quote: text,
source: source,
sourceURL: sourceURL,
translation: this._translationInput.getValue().trim(),
transcription: this._transcriptionInput.getValue().trim(),
// An edited line keeps its translation, and every parameter the form has no box for as long
// as it is still the same kind of line.
keepTranslation: !!this._editing,
extra: this._editing && this._editing.key === this._currentJederKey ? this._editing.example.extra : []
});
// definitionLevel[0] is "type|ordinal": the section's position among same-type headings within its
// language, resolved client-side, never MediaWiki's page-wide DOM id number.
var sectionKeyParts = this._definitionLevel[0].split("|");
var sectionType = sectionKeyParts[0];
var sectionOrdinal = parseInt(sectionKeyParts[1], 10);
if (this._editing) {
// A citation written out by hand is saved exactly as typed, its list marker included.
// Otherwise the rebuilt line keeps the page's own parameter order.
var replacement = usingRawTemplate
? (/^#/.test(text) ? text : this._editing.original.match(/^#+[:*]/)[0] + " " + text)
: cite.keepOrder(code, this._editing.original, this._editing.aliases);
this._sendReplacement(replacement, "±nimûneya bikaranînê bi amûra « {0} » (v{1}).".format(NAME, VERSION), reenable, error);
return;
}
// Read by the success handler below, which is a sibling of the transform, not nested in it.
var emptyTemplate = false;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = edit.util.SubmitAbort;
// api.edit() fetches the revision it will conflict-check against and hands it here, so the
// insertion runs on exactly that content. Named, so another gadget can run it instead.
function buildEdit(revision) {
var pageContent = revision.content;
// Said here rather than left to the locator: which sections this tool does not work in is
// this tool's own business, and the locator only knows that the key names no section.
if (!sections.headingNamesFor(sectionType)) {
throw new SubmitAbort('Navê beşê xelet e. Di beşên "Ji wêjêyê" û "Ji wêjêya klasîk" ev amûr nayê bikaranîn.');
}
// loc.lines covers the rest of the page from this language's heading on: the saved page is rebuilt
// from it below (see "text:").
var loc = wt.locateSection(pageContent, self._language, sectionType, sectionOrdinal);
var langSectionIndex = loc.langSectionIndex;
var lines = loc.lines;
var targetLineIndex = wt.findDefinitionLine(lines, loc.posLineIndex, self._definitionLevel);
// Where to insert inside this definition's "#:"/"#*" block. findBlockEnd() keeps multi-line
// templates whole; exampleTemplateNames tells a real example from an annotation.
var insertionFound = false;
var lastRelevantBlockEnd = -1;
var isJederFamily = (sablon === "jêder" || Object.prototype.hasOwnProperty.call(jederDefs, sablon));
var scanIndex = targetLineIndex + 1;
// Read off the definition line, so a sub-sense's examples are matched at its own depth and
// a deeper "#" line ends the block instead of being skipped past.
var annotation = annotationRegex(lines[targetLineIndex]);
var citation = citationRegex(lines[targetLineIndex]);
for (; scanIndex < lines.length; scanIndex++) {
var scanLine = lines[scanIndex];
if (!annotation.test(scanLine)) {
// Left this definition's own block (a sub-sense, a new definition, a new section).
break;
}
var blockEnd = findBlockEnd(lines, scanIndex);
var templateName = getLineTemplateName(scanLine);
var isExampleLine = templateName !== null &&
exampleTemplateNames.indexOf(templateName) !== -1;
if (isJederFamily) {
// A jêder-family example goes after every "#:"/"#*" line of this definition.
lastRelevantBlockEnd = blockEnd;
insertionFound = true;
} else if (sablon === "nimûne") {
// "nimûne" examples go before any "#*" line, but after "#:" lines.
if (citation.test(scanLine)) {
break;
}
lastRelevantBlockEnd = blockEnd;
insertionFound = true;
}
scanIndex = blockEnd; // Skip past the whole (possibly multi-line) block
}
var insertAt = lastRelevantBlockEnd >= 0 ? lastRelevantBlockEnd + 1 : scanIndex;
// After the relevant block, or straight after the definition when it had no "#:"/"#*" lines.
lines.splice(insertAt, 0, code);
// The "no example yet" marker goes once a real one lands. The whole "#:"/"#*" block is swept, not
// just its last line: the marker is often followed by other annotations.
var sweepFrom = targetLineIndex + 1;
for (var sweep = sweepFrom; sweep < lines.length; sweep++) {
if (!annotation.test(lines[sweep])) {
break;
}
if (EMPTY_EXAMPLE_MARKER.test(lines[sweep])) {
lines.splice(sweep, 1);
emptyTemplate = true; // tells the DOM update below to replace the marker's <li>
sweep--;
}
}
return {
text: pageContent.slice(0, langSectionIndex) + lines.join("\n"),
summary: "+nimûneya bikaranînê bi amûra « {0} » (v{1}).".format(NAME, VERSION),
};
}
// DefSideBoxes takes the save over when its panel opened this form: the edit joins that gadget's
// queue instead of publishing now.
var handOver = wikt.addExamples.submitHandler;
if (typeof handOver === "function") {
try {
handOver({
transform: buildEdit,
summary: "+nimûneya bikaranînê bi amûra « {0} » (v{1}).".format(NAME, VERSION),
done: function() {
self.setVisible(false);
self.clear();
self._clearDraft();
reenable();
},
fail: function(message) {
error(message || "Nikare li rêza guhartinan zêde bike.");
}
});
} catch (e) {
error("Nikare li rêza guhartinan zêde bike: " + (e && e.message ? e.message : e));
}
return;
}
api.edit(mw.config.get("wgPageName"), buildEdit).then(function() {
api.parse(code).done(function(data) {
var $renderedExample = $(data).find("ul > li").html();
var $item;
// Insert rendered example into page
if (emptyTemplate) {
self._$lastExample.html($renderedExample);
$item = self._$lastExample;
} else {
self._$lastExample.after($item = $("<li>").append($renderedExample));
}
$item.css("background-color", "lightgreen");
setTimeout(function() {
$item.css("background-color", "inherit");
}, 1000);
self._$lastExample = $item;
});
self.setVisible(false);
self.clear();
self._clearDraft();
reenable();
}, function(codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
error(codeOrAbort.message);
} else {
error(msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
function error(errorMessage) {
mw.notify(errorMessage || "Nimûne nehat weşandin, îhtimalen rûpel hate guhartin. Ji kerema xwe re rûpelê dîsa bar bike û dîsa biceribîne.");
// A draft is kept for a new example only: an edited one would come back on the next new one.
if (!self._editing) {
draftStorage.Set({
text: self._textInput.getValue(),
source: self._sourceInput.getValue(),
sourceURL: self._sourceURLInput.getValue(),
translation: self._translationInput.getValue(),
transcription: self._transcriptionInput.getValue(),
});
}
reenable();
}
function reenable() {
self._textInput.setDisabled(false);
self._sourceInput.setDisabled(false);
self._sourceURLInput.setDisabled(false);
self._translationInput.setDisabled(false);
self._transcriptionInput.setDisabled(false);
self._sourceTypeDropdown.setDisabled(false);
Object.keys(self._jederFieldWidgets).forEach(function(key) {
self._jederFieldWidgets[key].setDisabled(false);
});
// self._disableTranslationChk.setDisabled(false);
self._applyButton.setDisabled(false);
self._cancelButton.setDisabled(false);
self._clearButton.setDisabled(false);
self._loadingImage.toggle(false);
}
},
};
});
// </nowiki>
6ve7nttnywf6sa4b60t8a7cwm4mf6rv
6534983
6534964
2026-09-20T19:19:34Z
Wikihez
7344
DefSideBoxes: the new sense gets its own numbered row, its own buttons and its own quotations
6534983
javascript
text/javascript
// <nowiki>
// dependencies (in MediaWiki:Gadgets-definition): oojs, oojs-ui, ext.gadget.citoid-helper,
// ext.gadget.StorageUtils, ext.gadget.wikt.core, ext.gadget.wikt.edit, ext.gadget.wikt.cite
//
// A ResourceLoader package module: it require()s wikt.cite, so it cannot be loaded as a plain
// script. Load it through its gadget definition or the test loader.
//
// Two copies at once would fight over wikt.addExamples, so the second to arrive does nothing.
window.wikt = window.wikt || {};
wikt.addExamples = wikt.addExamples || {};
var alreadyLoaded = wikt.addExamples.loaded;
wikt.addExamples.loaded = true;
// Formats a string using {0}, {1}, ... placeholders.
if (!String.prototype.format) {
String.prototype.format = function(var_args) {
var args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] !== "undefined" ? args[number] : match;
});
};
}
$(function() {
"use strict";
if (alreadyLoaded) return;
// Entries, reconstructions and sandboxes -- the same set DefSideBoxes and the wizard run on, from
// wikt.core, so a page one of them works on is never a page this one quietly skips.
if (!require("ext.gadget.wikt.core").sections.isEntryPage()) return;
if (mw.config.get("wgPageName") === "Destpêk" || mw.config.get("wgArticleId") === 0) return;
var NAME = "[[Mediawiki:Gadget-wikt.add-examples|Nimûne zêde bike]]";
// This gadget puts nothing on the page of its own. DefSideBoxes opens the form from its own
// "Nimûne/jêgirtin" item, through openFor()/toggleFor() below, and takes the save over.
window.wikt = window.wikt || {};
wikt.addExamples = wikt.addExamples || {};
// Lets another gadget take the save over: "Qeyd bike" becomes msg.add and the edit joins
// that gadget's queue. Pass null to hand publishing back. Handler gets {transform, summary, done, fail}.
wikt.addExamples.setSubmitHandler = function (fn) {
wikt.addExamples.submitHandler = typeof fn === "function" ? fn : null;
};
// Re-clicking the caller's own trigger closes the form again.
wikt.addExamples.toggleFor = function (el) {
var toggle = $(el).data("wiktAddExampleToggle");
if (!toggle) {
return false;
}
toggle();
return true;
};
wikt.addExamples.openFor = function (el) {
var open = $(el).data("wiktAddExampleOpen");
if (!open) {
return false;
}
open();
return true;
};
// Opens the form on one example already under the sense el: itemEl is its <dd>/<li> on the page.
wikt.addExamples.editFor = function (el, itemEl) {
var editItem = $(el).data("wiktAddExampleEdit");
return !!editItem && editItem(itemEl);
};
var VERSION = "2.0";
// Wiki markup for bold, both the button's label and what it inserts.
var BOLD_MARKS = "'''";
// Persists form drafts (localStorage, cookie fallback) so unsaved text survives a failed submit.
var draftStorage = new ObjectStorage("add_examples_draft", 1);
// Citations are built by wikt.cite, the same module the wizard uses; Citoid lives in the
// citoid-helper gadget. "citoid" as a _currentJederKey is a sentinel, not a real jederDefs key.
var cite = require("ext.gadget.wikt.cite");
var core = require("ext.gadget.wikt.core");
var sections = core.sections;
var msg = core.msg;
var edit = require("ext.gadget.wikt.edit");
var jederDefs = cite.jederDefs;
var nlToSpace = cite.nlToSpace;
var boldenPageTitle = cite.boldenPageTitle;
var CATEGORY_TEMPLATE_KEY = cite.CATEGORY_KEY;
var RAW_TEMPLATE_KEY = cite.RAW_KEY;
// Params the form already has boxes for; skipped when building a Kategorî:Çavkanî template's fields.
var CATEGORY_TEMPLATE_COVERED_PARAMS = cite.COVERED_PARAMS;
// A param labeled exactly this is filled from the language dropdown instead of shown as a field,
// and only when the template's own TemplateData declares one.
var CATEGORY_TEMPLATE_LANG_LABEL = cite.LANG_LABEL;
// "span" is the field's width in the 12-column grid the fields render in (see .add-example-jeder-fields
// in the CSS and _setSourceType below) -- field ORDER here is also the grid's reading order.
// Heading/definition-list helpers and the recognized POS heading names, shared with the wizard and
// DefSideBoxes so all three read the same page structure.
var headingOuter = sections.headingOuter;
var languageCodeForOl = sections.languageCodeForOl;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var eachLeafDefinition = sections.eachLeafDefinition;
// A heading's section key, the wizard's unclassified "=== Mane ===" placeholder included: it holds
// real definitions, so examples belong in it, even though it is not a part of speech.
var sectionKeyOf = sections.sectionKeyOf;
// Locating a section and a definition inside it is wikt.edit's job, shared with DefSideBoxes.
var wt = edit.wt;
// Template names that count as real examples, so an annotation template is not mistaken for one.
var exampleTemplateNames = ["jêder"].concat(cite.USEX_NAMES, Object.keys(jederDefs));
// The "no example yet" placeholder, with or without a language parameter.
var EMPTY_EXAMPLE_MARKER = cite.EMPTY_EXAMPLE_MARKER;
// The template name starting a "#:"/"#*" line, or null when the line does not start with one.
function getLineTemplateName(line) {
var m = /^#[:*]\s*\{\{\s*([^|}]+)/.exec(line);
return m ? m[1].trim() : null;
}
// Where a template block starting at startIndex closes its braces, so multi-line calls stay whole.
// The "#" run a definition line carries, so a sense's own annotations can be told from a sub-sense's:
// "#:" belongs to "#", "##:" to "##".
function senseDepth(definitionLine) {
var m = /^#+/.exec(definitionLine || "");
return m ? m[0].length : 1;
}
// Any annotation of that sense -- "#:" an example, "#*" a citation -- at its own depth and no other.
function annotationRegex(definitionLine) {
return new RegExp("^#{" + senseDepth(definitionLine) + "}[:*]");
}
function citationRegex(definitionLine) {
return new RegExp("^#{" + senseDepth(definitionLine) + "}\\*");
}
function findBlockEnd(lines, startIndex) {
var depth = 0;
var i = startIndex;
for (; i < lines.length; i++) {
var opens = (lines[i].match(/\{\{/g) || []).length;
var closes = (lines[i].match(/\}\}/g) || []).length;
depth += opens - closes;
if (depth <= 0) {
break;
}
}
return i;
}
// URL of a template's own wiki page -- lets the user check a Kategorî:Çavkanî search result
// before picking it (see showResults below).
// Escapes str for interpolation into a RegExp: page titles carry regex metacharacters.
var escapeRegExp = edit.util.escapeRegExp;
// The readable half of an mw.Api rejection.
var apiErrorMessage = edit.util.apiErrorMessage;
// Same marker as the wizard's, positioned inside OOUI's own wrapper rather than a new one.
var highlightLinebreak = edit.util.makeLinebreakHighlighter({
prefix: "add-example",
wrap: false,
dataKey: "addExampleLinebreakBackdrop",
title: "Xeta n\u00fb d\u00ea wek <br> b\u00ea niv\u00eesandin"
});
// Linebreak marking on the quote text and its translation/transcription only, not the
// bibliographic fields.
function watchForLinebreaks(widget) {
widget.on("change", function() {
highlightLinebreak(widget.$input[0]);
});
}
// The shared grip from wikt.edit, kept as a sibling inside OOUI's own wrapper and without the saved
// height: these fields are plain MultilineTextInputWidgets with no class to key one by.
function makeManualResizable(textarea) {
edit.util.makeManualResizable(textarea, {
wrap: false,
persist: false,
gripClass: "add-example-resize-grip",
doneKey: "addExampleResizableDone"
});
}
// Applies makeManualResizable to every textarea in `context` (itself or its descendants).
function initManualResize(context) {
var $context = $(context);
var $textareas = $context.is("textarea") ? $context : $context.find("textarea");
$textareas.each(function() {
makeManualResizable(this);
});
}
var api = new mw.Api();
// Kategorî:Çavkanî's templates and their redirects, each redirect its own entry pointing at the
// Language display name, read from the matching section's .besazimani link text.
function languageNameForCode(code) {
var $mark = $(".besazimani[id='" + code + "']").first();
return $mark.length ? $mark.text().trim() : null;
}
// Whether the page title itself (accented Kurdish Latin letters included) is written in Latin script.
var pageTitleIsLatin = !/[^\x00-\x7FÀ-ɏḀ-ỿ‘’]/.test(mw.config.get("wgTitle") || "");
// What an example looks like once rendered: Şablon:jêder's old marker, Modul:usex's containers and
// Modul:quote's wrapper.
var EXAMPLE_SELECTOR = ".example, .benimune, .h-usage-example, .h-quotation, .citation-whole";
// The words drawn for one example: the example or quotation itself, never its translation or source.
function exampleTextOf($item) {
var $own = $item.find(".e-example, .e-quotation").first();
if ($own.length) {
return $own.text();
}
var $italic = $item.children("i").first();
return ($italic.length ? $italic : $item.clone().children("dl, ul, .dsb-example-toolbar").remove().end()).text();
}
// A definition's own citation group, and the last one when it has several: a new citation is
// written after every "#:"/"#*" line already there.
function exampleGroupOf($li) {
return $li.children("ul").filter(function () {
return $(this).children("li").children(EXAMPLE_SELECTOR).length > 0;
}).last();
}
// Every recognized part-of-speech section, down to each of its definitions. Section, language and
// nesting all come from the page's own structure, never from an example that happens to sit there.
// "===" on most entries, "====" where the senses hang off numbered etymologies.
$("h3, h4").each(function() {
var sectionType = sectionKeyOf($(this));
if (!sectionType) {
return; // not a recognized word-sense section (Bilêvkirin, Etîmolojî, Çavkanî, ...)
}
var sectionOrdinal = sectionOrdinalWithinLanguage($(this));
// Forward-walk siblings to the definitions <ol>, skipping over a declension table/TemplateStyles/the
// headword line; stop (nothing to attach to in this section) if another heading comes first instead.
var $ol = null;
for (var $cursor = headingOuter($(this)).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("ol")) {
$ol = $cursor;
break;
}
if (sections.headingDepth($cursor)) {
break;
}
}
if (!$ol) {
return;
}
var language = languageCodeForOl($ol);
if (!language) {
console.warn("Gadget-wikt.add-examples: nikaribe koda zimanê vê beşê bibîne.", $ol);
return;
}
eachLeafDefinition($ol, function($li) {
var definitionLevel = [];
var $definitionItem = $li;
while ($definitionItem.prop("tagName") === "LI") {
// Skip MediaWiki's empty <li class="mw-empty-elt"> so indices match the "#" lines in wikitext.
definitionLevel.splice(0, 0, $definitionItem.prevAll("li:not(.mw-empty-elt)").length);
$definitionItem = $definitionItem.parent().parent();
}
definitionLevel.splice(0, 0, sectionType + "|" + sectionOrdinal);
// Where a submitted example lands: after the group's last example, or after a hidden placeholder
// when the definition has no group.
var $group = exampleGroupOf($li);
var $placeholder = $group.length ? $group.children("li").last()
: $("<li style='display:none'></li>");
var $formItem = $("<li>");
// Never in the DOM: Form toggles it against the title, so it stays as the "is a form built
// for this sense" handle. Nothing on the page opens this gadget -- DefSideBoxes does.
var $button = $("<span>").hide();
// Says which sense and language the open form is for; the row exists only while it is open.
var $title = $("<span class='add-example-form-title'></span>").hide();
$formItem.append($title);
// The group the button lives in: the one on the page, or a new one. A "ul" under a definition
// "li" starts hidden, so a new one needs the explicit display override.
var $ul = $group.length ? $group
: $("<ul>").css("display", "block").append($placeholder);
// A citation already carrying the site's own small edit link does not need a second one.
$ul.find("span.example > span.stubedit").remove();
// The panel opens after $ul, not inside it: full width, clear of the list's hanging indent.
// edit, when given, is {index, text}: the form opens on that example instead of on a new one.
function setFormVisible(visible, edit) {
if (visible) {
// A sense with no example list of its own gets one on first open, not on page load:
// an empty list on every sense of every entry is margin nobody asked for.
// Editing shows its title inside the form, so the list row is only for a new example.
if (!edit && !$ul.parent().length) {
$li.append($ul);
}
if (!edit && !$formItem.parent().length) {
$ul.append($formItem);
}
if (edit) {
$formItem.detach();
}
if (!$button.form) {
$title.text("Nimûneyeke bi " + (languageNameForCode(language) || "zimanekî nenas").toLowerCase() + " lê zêde dikî.");
var form = new Form($placeholder, $button, $title, language, definitionLevel);
form.onHide = function () {
$formItem.detach();
};
$ul.after(form.$element);
}
// Right under the example being edited; after the whole list when adding a new one.
(edit ? $(edit.item) : $ul).after($button.form.$element);
if (edit) {
$button.form.beginEdit(edit.index, edit.text);
} else {
$button.form.endEdit();
$button.form.setVisible(true);
}
} else if ($button.form) {
$button.form.setVisible(false);
$formItem.detach();
}
return false;
}
function openForm() { return setFormVisible(true); }
function toggleForm() { return setFormVisible(!($button.form && $button.form.isVisible())); }
$li.data("wiktAddExampleOpen", openForm);
$li.data("wiktAddExampleToggle", toggleForm);
// Its place among the sense's examples finds the line; the words drawn for it confirm it.
$li.data("wiktAddExampleEdit", function (itemEl) {
var $item = $(itemEl);
var index = sections.exampleItemsOf($li).index($item);
if (index === -1) {
return false;
}
setFormVisible(true, { index: index, text: exampleTextOf($item), item: itemEl });
return true;
});
});
});
// Pulls the form panel out to the page's own edges, undoing the nested list's indent.
function alignFormToPage($form) {
var $container = $form.closest(".mw-parser-output");
if (!$container.length) {
return;
}
// Measure unshifted: this runs on every re-open, and a margin left from last time would otherwise
// count as part of the indent and cancel the alignment out.
$form.css("marginLeft", 0);
var pad = parseFloat(window.getComputedStyle($container[0]).paddingLeft) || 0;
var containerLeft = $container.offset().left + pad;
var indent = $form.offset().left - containerLeft;
$form.css({
marginLeft: indent > 0 ? -indent : 0,
width: $container.width() + "px"
});
}
/**
* language is the section the sense sits in: what the new example is tagged with, and what the
* wikitext lookup searches for. Never the language of an example that happens to be there.
*/
function Form($lastExample, $button, $title, language, definitionLevel) {
var self = this;
this._language = language;
this._definitionLevel = definitionLevel;
this._$lastExample = $lastExample;
this._$button = $button;
this._$button.form = this;
this._$title = $title;
// Where the title lives when adding; while editing it moves into the form itself.
this._$titleHome = $title.parent();
this._currentJederKey = "";
this._jederFieldWidgets = {};
// Set once a template is picked in the Kategorî:Çavkanî mode (see _selectCategoryTemplate); the
// canonical template title (without the "Şablon:" prefix), used as `sablon` in _doSubmit.
this._categoryTemplateName = null;
// Set by _renderCategoryTemplateFields if the picked template's TemplateData has a "koda zimanî" param;
// that param's own key, used to emit "<key>=<language>" in _doSubmit -- null if no such param exists.
this._categoryTemplateLangParamKey = null;
// Set while an example already on the page is being edited (see beginEdit): {index, original,
// example, key}, where key is the source type the line was read into.
this._editing = null;
this._addTitle = null;
// The same prompt the wizard's own example box carries, so the two ask for an example in one
// wording and the language it is to be written in is on screen here too.
this._textInput = new OO.ui.MultilineTextInputWidget({
rows: 4,
placeholder: edit.util.examplePrompt(languageNameForCode(language), false)
});
var textInputLayout = new OO.ui.FieldLayout(this._textInput, {
label: "Metna nimûneyê",
align: "top",
});
// Under the text box: what will happen to the entry's own name on save (see boldenPageTitle), and
// the one markup button worth having beside it. Purely informational, never blocks submitting.
this._titleBoldHint = $("<span class='add-example-title-bold-hint'></span>");
this._updateTitleBoldHint = function() {
var text = self._textInput.getValue().trim();
var title = mw.config.get("wgTitle");
var missing = !!text && !boldenPageTitle(text, title).bolded;
self._titleBoldHint
.toggleClass("add-example-title-bold-warn", missing)
.html(missing
? "Sermade (" + mw.html.escape(title) + ") nehat dîtin; dê neyê <b>stûr</b>kirin."
: "Sermade otomatîk tê <b>stûr</b>kirin.");
// Only where it is needed: when the name is bolded on its own there is nothing to reach for.
self._boldButton.toggle(missing);
};
// Shown only while the name cannot be bolded on its own -- a form the matcher does not recognise,
// or another word the writer wants bolded.
this._boldButton = $("<button type='button' class='add-example-bold-btn'></button>").hide()
.text(BOLD_MARKS).attr("title", "Nivîsa hilbijartî stûr bike");
this._boldButton.on("click", function(e) {
e.preventDefault();
var input = self._textInput.$input[0];
var value = input.value;
var start = input.selectionStart;
var end = input.selectionEnd;
var next, caret;
if (start !== end) {
next = value.slice(0, start) + BOLD_MARKS + value.slice(start, end) + BOLD_MARKS + value.slice(end);
caret = end + BOLD_MARKS.length * 2;
} else {
// Nothing selected: just the marks, where the caret is. Typing carries on after them.
next = value.slice(0, start) + BOLD_MARKS + value.slice(start);
caret = start + BOLD_MARKS.length;
}
self._textInput.setValue(next);
input.focus();
input.setSelectionRange(caret, caret);
});
this._textTools = $("<div class='add-example-text-tools'></div>")
.append(this._boldButton, this._titleBoldHint);
this._updateTitleBoldHint();
// A pasted citation switches to raw mode rather than being wrapped in a second {{nimûne}}: a whole
// line with its marker and a bare template call both count. Only while the type is still citoid.
this._maybeSwitchToRaw = function(value) {
if (self._currentJederKey !== "citoid" ||
!(cite.looksLikeCitationLine(value) || cite.looksLikeTemplate(value))) {
return;
}
var menu = self._sourceTypeDropdown.getMenu();
var item = menu.findItemFromData(RAW_TEMPLATE_KEY);
if (item) {
menu.chooseItem(item); // calls _setSourceType() itself
}
};
this._textInput.on("change", function(value) {
self._applyButton.setDisabled(!value);
self._maybeSwitchToRaw(value);
self._updateReadyHint();
self._updateTitleBoldHint();
});
watchForLinebreaks(this._textInput);
// Source type: free text, a jêder-* template, or "citoid" -- a sentinel meaning "not yet detected",
// switched automatically once a Citoid fetch succeeds.
var sourceTypeMenuItems = [
new OO.ui.MenuOptionWidget({ data: "citoid", label: msg.citoidAuto }),
new OO.ui.MenuOptionWidget({ data: "", label: msg.sourceByHand }),
];
for (var jederKey in jederDefs) {
if (jederDefs.hasOwnProperty(jederKey)) {
sourceTypeMenuItems.push(new OO.ui.MenuOptionWidget({
data: jederKey,
label: jederDefs[jederKey].label,
}));
}
}
sourceTypeMenuItems.push(new OO.ui.MenuOptionWidget({
data: CATEGORY_TEMPLATE_KEY,
label: msg.sourceFromCategory,
}));
// Paste a finished citation into the example box and have it saved exactly as typed -- the quickest
// way to reuse one jêgirtin across several entries.
sourceTypeMenuItems.push(new OO.ui.MenuOptionWidget({
data: RAW_TEMPLATE_KEY,
label: msg.sourceRaw,
}));
// Shown only in raw mode, where there is nothing else to fill in.
this._rawTemplateHint = new OO.ui.MessageWidget({
type: "notice",
inline: true,
label: "Şablona temam li qada \"Metna nimûneyê\" binivîse \u2014 wek xwe tê qeydkirin. "
+ "Tenê navê maddeyê yê di nav jêgirtin= de tê stûrkirin.",
classes: ["add-example-raw-hint"],
});
this._rawTemplateHint.toggle(false);
this._sourceTypeDropdown = new OO.ui.DropdownWidget({
menu: { items: sourceTypeMenuItems },
label: msg.citoidAuto,
});
var sourceTypeLayout = new OO.ui.FieldLayout(this._sourceTypeDropdown, {
label: "Şablona çavkaniyê",
align: "top",
});
this._sourceTypeDropdown.getMenu().on("choose", function(menuOption) {
self._setSourceType(menuOption.getData());
});
this._sourceInput = new OO.ui.MultilineTextInputWidget();
this._sourceInput.on("change", function() {
self._updateReadyHint();
});
this._sourceInputLayout = new OO.ui.FieldLayout(this._sourceInput, {
label: "Çavkaniya nimûneyê",
align: "top",
});
// Visible in every mode: submit() reads it as "url=" regardless. The Citoid button sits beside it.
this._sourceURLInput = new OO.ui.TextInputWidget({
icon: "link",
placeholder: "https://… an jî DOI/ISBN",
});
this._citoidButton = new OO.ui.ButtonWidget({
label: msg.citoidFill,
icon: "download",
flags: ["progressive"],
title: "Li gorî URL, DOI an ISBNya jorîn, malûmatan ji Citoid tîne û qadên şablona jêder-* otomatîk tijî dike.",
classes: ["add-example-citoid-fetch"],
});
this._citoidButton.on("click", this._fillFromCitoid.bind(this));
this._sourceURLInput.on("change", function() {
self._updateReadyHint();
self._autofillVideoTimestamp();
});
// URL input and its auto-fill button as one flex row; a bare OO.ui.Widget used as the container.
this._urlRowWidget = new OO.ui.Widget({ classes: ["add-example-url-row"] });
this._urlRowWidget.$element.append(this._sourceURLInput.$element, this._citoidButton.$element);
this._sourceURLInputLayout = new OO.ui.FieldLayout(this._urlRowWidget, {
label: "Lînk, DOI an ISBNya çavkaniyê",
align: "top",
help: "Li qutiyê URL, DOI an ISBN binivîse û pê lê bike butona '" + msg.citoidFill + "' da ku otomatîk çavkanî were dagirtin.",
helpInline: true,
});
// Same wording as above, so the initial _setSourceType("citoid") rebuilds nothing.
this._urlFieldIsFreeTextWording = false;
// Shown right after a successful Citoid fetch (see _fillFromCitoid); hidden again on clear.
this._citoidSuccessMsg = $("<span class='add-example-citoid-success'></span>").text(msg.citoidFilled).hide();
// Clears the URL field and any filled jêder-* fields without touching the example text/translation.
this._citoidClearButton = new OO.ui.ButtonWidget({
label: "Vala bike",
icon: "trash",
framed: false,
title: "Lînkê û qadên jêder-* yên vê beşê vala bike",
classes: ["add-example-citoid-clear"],
});
this._citoidClearButton.on("click", function() {
self._sourceURLInput.setValue("");
self._citoidSuccessMsg.hide();
Object.keys(self._jederFieldWidgets).forEach(function(key) {
self._jederFieldWidgets[key].setValue("");
});
});
// Plain div (not OO.ui.FieldsetLayout) so the grid CSS class applies directly to the field items.
this._jederFieldsFieldset = $("<div class='add-example-jeder-fields'>");
// Header and clear button above the fields grid. Only meaningful once a real jêder-* template is
// active, so the whole section toggles together in _setSourceType().
this._jederSection = $("<div class='add-example-jeder-section'>").append(
$("<div class='add-example-jeder-header'>")
.append($("<span class='add-example-jeder-header-label'>Malûmatên çavkaniyê</span>"))
.append(this._citoidClearButton.$element),
this._jederFieldsFieldset
);
// wikt.cite's picker, the one the wizard and DefSideBoxes show. Sits under the source-type
// dropdown, not in the fields section, and is built once so switching modes keeps its handlers.
var categoryPicker = cite.buildCategoryTemplateSearch({
describe: true,
onPick: function (entry) {
self._selectCategoryTemplate(entry);
}
});
this._categoryTemplateInput = categoryPicker.$input;
this._categoryTemplateStatus = categoryPicker.$status;
this._categoryTemplateSearchRow = categoryPicker.$element
.addClass("add-example-cat-tpl-search-row").hide();
this._translationInput = new OO.ui.MultilineTextInputWidget({ rows: 4 });
var translationInputLayout = new OO.ui.FieldLayout(this._translationInput, {
// A Kurmancî example gets an explanation rather than a translation.
label: language === "ku" ? "Îzaheke kurmancî ya nimûneyê" : "Tercimeya kurmancî ya nimûneyê",
align: "top",
});
watchForLinebreaks(this._translationInput);
this._transcriptionInput = new OO.ui.MultilineTextInputWidget({ rows: 4 });
var transcriptionInputLayout = new OO.ui.FieldLayout(this._transcriptionInput, {
label: "Translîterasyona nimûneyê",
align: "top",
help: "Tenê lê zêde bike eger metna di nimûneyê de bi alfabeya latînî nehatibe nivîsandin.",
helpInline: true,
});
watchForLinebreaks(this._transcriptionInput);
this._applyButton = new OO.ui.ButtonWidget({
label: "Qeyd bike",
icon: "check",
title: "Nimûneyê ji bo vê tarîfê nimûneyê lê zêde bike",
flags: ["progressive", "primary"],
disabled: true,
});
this._applyButton.on("click", this.submit.bind(this));
// Reactive "ready to submit" hint next to Qeyd bike: no text yet, text but no source (still submittable
// as a bare {{nimûne}}), or fully ready -- kept in sync by _updateReadyHint(), wired to every field below.
this._readyHint = $("<span class='add-example-ready-hint'></span>").text("Metna nimûneyê lazim e.");
// True while a Citoid-eligible URL is typed but unfetched and no jêder field has been filled by
// hand -- the "looks done but isn't" case.
this._citoidPending = function() {
return self._currentJederKey !== "" &&
self._currentJederKey !== CATEGORY_TEMPLATE_KEY &&
self._currentJederKey !== RAW_TEMPLATE_KEY &&
!!self._sourceURLInput.getValue().trim() &&
!self._citoidSuccessMsg.is(":visible") &&
!Object.keys(self._jederFieldWidgets).some(function(key) {
return self._jederFieldWidgets[key].getValue().trim();
});
};
// A Kurmancî jêgirtin takes no translation or explanation, only a {{nimûne}} does. An edited line
// that already has one keeps its box.
this._syncTranslationBox = function() {
if (self._language !== "ku") {
return;
}
var key = self._currentJederKey;
var isQuote = key === RAW_TEMPLATE_KEY || key === CATEGORY_TEMPLATE_KEY || !!jederDefs[key] ||
!!self._sourceInput.getValue().trim() || !!self._sourceURLInput.getValue().trim();
translationInputLayout.toggle(!isQuote ||
!!(self._editing && self._translationInput.getValue().trim()));
};
this._updateReadyHint = function() {
self._syncTranslationBox();
if (!self._textInput.getValue()) {
self._readyHint.text("Metna nimûneyê lazim e.");
return;
}
if (self._citoidPending()) {
self._readyHint.text("Pêşî '" + msg.citoidFill + "' bitikîne.");
return;
}
// Raw mode carries its own source inside the pasted template, so there is nothing else to ask for.
if (self._currentJederKey === RAW_TEMPLATE_KEY) {
self._readyHint.text("Hazir e.");
return;
}
var hasSource = self._sourceInput.getValue().trim() || self._sourceURLInput.getValue().trim() ||
Object.keys(self._jederFieldWidgets).some(function(key) {
return self._jederFieldWidgets[key].getValue().trim();
});
self._readyHint.text(hasSource ? "Hazir e." : "Çavkanî nehatiye dayîn.");
};
// Prefills Dem from the URL's own "t=" once jêder-vîdeo is active; never overwrites typed text.
this._autofillVideoTimestamp = function() {
var demWidget = self._jederFieldWidgets.dem;
if (self._currentJederKey !== "jêder-vîdeo" || !demWidget || demWidget.getValue().trim()) {
return;
}
var dem = wikt.citoid.extractVideoTimestamp(self._sourceURLInput.getValue().trim());
if (dem && !demWidget.getValue().trim()) {
demWidget.setValue(dem);
}
};
// framed:false (+ the "destructive" flag for the red text/hover) matches the mockup's borderless
// "Betal bike" -- a plain colored link rather than a boxed button, distinguishing it from Paqij bike.
this._cancelButton = new OO.ui.ButtonWidget({
label: "Betal bike",
icon: "close",
title: "Formê bigire",
flags: ["destructive"],
framed: false,
});
this._cancelButton.on("click", function() {
self.setVisible(false);
});
this._clearButton = new OO.ui.ButtonWidget({
label: "Paqij bike",
icon: "trash",
title: "Hemû qadan vala bike û pêşnûyisên hilanîbûyî jê bibe",
});
this._clearButton.on("click", function() {
self.clear();
self._clearDraft();
});
// Only while an example on the page is being edited.
this._deleteButton = new OO.ui.ButtonWidget({
label: "Jê bibe",
icon: "trash",
flags: ["destructive"],
});
this._deleteButton.toggle(false);
this._deleteButton.on("click", function() {
OO.ui.confirm("Ev nimûne dê were rakirin. Dewam bike?").done(function(ok) {
if (ok) {
self._submitDelete();
}
});
});
// A CSS spinner rather than a Commons GIF: no request, and it follows dark/light mode.
this._loadingImage = new OO.ui.Widget({
classes: ["add-example-loading"],
});
// The ring alone says nothing to a reader who has asked for less motion: MediaWiki's own
// reduced-motion reset cuts every animation to 0.01ms, so it never turns for them. The words
// beside it carry the state either way.
this._loadingImage.$element.append(
$('<span class="add-example-spinner" aria-hidden="true"></span>'),
$('<span class="add-example-loading-label"></span>').text(msg.loading)
);
this._loadingImage.toggle(false);
// Two-column top: example text (with translation/transliteration for non-ku) on the left, source
// type and link on the right. Plain divs, so the CSS can lay them out as a grid.
// The translation box is there for Kurmancî too: {{nimûne}} takes one for every language.
var $topLeft = $("<div class='add-example-top-col'>").append(textInputLayout.$element, this._textTools,
translationInputLayout.$element);
if (!pageTitleIsLatin) {
$topLeft.append(transcriptionInputLayout.$element);
}
// Kept for _fill, which adds either box when an edited line already carries one.
this._$topLeft = $topLeft;
this._translationInputLayout = translationInputLayout;
this._transcriptionInputLayout = transcriptionInputLayout;
var $topRight = $("<div class='add-example-top-col'>").append(
sourceTypeLayout.$element,
this._categoryTemplateSearchRow,
this._rawTemplateHint.$element,
this._sourceInputLayout.$element,
this._sourceURLInputLayout.$element,
this._citoidSuccessMsg
);
var $topGrid = $("<div class='add-example-top-grid'>").append($topLeft, $topRight);
// Saved on the instance (not just a local var) so _setSourceType() can swap the URL field's label/help
// wording in and out of it when switching to/from free text (see the FieldLayout replaceWith() there).
this._fieldsLayout = $("<div class='add-example-fieldset'>").append($topGrid, this._jederSection);
// Bottom action row: Qeyd bike (+ spinner and hint), a spacer, then Paqij bike and Betal bike. A
// flex div rather than an OOUI layout, so the hint and spacer can sit among the widgets.
var $buttonsRow = $("<div class='add-example-main-buttons'>").append(
this._applyButton.$element,
this._loadingImage.$element,
this._readyHint,
$("<span class='add-example-buttons-spacer'></span>"),
this._deleteButton.$element,
this._clearButton.$element,
this._cancelButton.$element
);
this._frame = new OO.ui.PanelLayout({
id: "add-example-definition-{0}-form".format(this._definitionLevel.join("-")),
classes: ["add-example-form"],
expanded: false,
});
this._frame.$element.append(this._fieldsLayout, $buttonsRow);
// selectItemByData() does not fire "choose", so the initial mode is set up by hand.
this._sourceTypeDropdown.getMenu().selectItemByData("citoid");
this._setSourceType("citoid");
initManualResize(this._frame.$element);
}
Form.prototype = {
// Returns the jQuery object for this form.
get $element() {
return this._frame.$element;
},
// Switches the source section between free text, Citoid auto-detect and a chosen jêder-* template,
// rebuilding the field list. The URL field stays visible in every mode.
_setSourceType: function(jederKey) {
var self = this;
this._currentJederKey = jederKey;
this._jederFieldsFieldset.empty();
this._jederFieldWidgets = {};
this._categoryTemplateName = null;
this._categoryTemplateLangParamKey = null;
var isRealJederKey = jederKey && jederDefs[jederKey];
var isCategoryTemplateKey = jederKey === CATEGORY_TEMPLATE_KEY;
var isRawTemplate = jederKey === RAW_TEMPLATE_KEY;
if (isRealJederKey) {
var def = jederDefs[jederKey];
def.fields.forEach(function(field) {
var widget = field.multiline
? new OO.ui.MultilineTextInputWidget({ rows: 2, autosize: false })
: new OO.ui.TextInputWidget();
self._jederFieldWidgets[field.key] = widget;
widget.on("change", function(value) {
// sal and tarîx say the same thing twice and Modul:quote refuses both; filling one
// clears the other rather than letting the citation break on save.
var partner = value && cite.exclusivePartner(field.key);
if (partner && self._jederFieldWidgets[partner]) {
self._jederFieldWidgets[partner].setValue("");
}
self._updateReadyHint();
});
var layout = new OO.ui.FieldLayout(widget, {
label: field.label,
align: "top",
classes: ["add-example-field-span-" + (field.span || 4)],
});
self._jederFieldsFieldset.append(layout.$element);
});
}
// The box lives in $topRight always; here it is only shown for this mode, and reset on the way out
// so a stale query does not linger.
this._categoryTemplateSearchRow.toggle(isCategoryTemplateKey);
// Raw mode has no source fields of its own: the citation is already written out in full.
this._rawTemplateHint.toggle(isRawTemplate);
if (!isCategoryTemplateKey) {
this._categoryTemplateInput.val("");
}
// The fields grid is for a real jêder-* template. The Kategorî:Çavkanî picker shows it only once a
// template is picked (see _selectCategoryTemplate).
this._jederSection.toggle(!!isRealJederKey);
// Free text textarea only for "" (free text); hidden for "citoid" and any real jêder-* key.
this._sourceInputLayout.toggle(jederKey === "");
// No Citoid button for free text (nothing to fill) or the Kategorî:Çavkanî picker (no mapping for
// an arbitrary template); shown for auto-detect and any jêder-* key.
this._citoidButton.toggle(jederKey !== "" && !isCategoryTemplateKey && !isRawTemplate);
// Plain "attach a link" wording for free text, Citoid wording otherwise. Rebuilt only when crossing
// that boundary.
var isFreeText = jederKey === "";
if (this._urlFieldIsFreeTextWording !== isFreeText) {
this._urlFieldIsFreeTextWording = isFreeText;
// On the <input> itself: TextInputWidget has no setter for its placeholder.
this._sourceURLInput.$input.attr("placeholder", isFreeText ? "URLyeke wekî https://…" : "URLyek wekî https://… an jî DOI/ISBN");
var $oldLayout = this._sourceURLInputLayout.$element;
// Re-wraps the SAME _urlRowWidget (still holding _sourceURLInput + _citoidButton) in a new
// FieldLayout with the new label/help; the widget's $element moves along with it.
this._sourceURLInputLayout = new OO.ui.FieldLayout(this._urlRowWidget, isFreeText ? {
label: "Adresa webê ya nimûneyê",
align: "top",
help: "Tenê binivîse eger lînk jixwe di referansa çavkaniyê de tinebe.",
helpInline: true,
} : {
label: "Lînk, DOI an ISBNya çavkaniyê",
align: "top",
help: "Ev dibe lînka çavkaniyê (url=). Ji bo şablonên jêder-*, dikare bi Citoid an bi destan jî bên dagirtin.",
helpInline: true,
});
$oldLayout.replaceWith(this._sourceURLInputLayout.$element);
}
// The URL/Citoid row is normally always visible (see the constructor comment), but Citoid has no
// mapping for an arbitrary Kategorî:Çavkanî template, so it'd just be dead UI in that one mode.
this._sourceURLInputLayout.toggle(!isCategoryTemplateKey && !isRawTemplate);
this._autofillVideoTimestamp();
this._updateReadyHint();
initManualResize(this._jederFieldsFieldset); // covers a freshly-rebuilt "sernav" field (jêder-*); the Kategorî:Çavkanî picker's own fields are resized separately once picked, see _renderCategoryTemplateFields
},
// Wires the search box's handlers and starts the template fetch. Called once: the box lives in
// $topRight and is only shown or hidden by _setSourceType.
// Records the picked template's canonical name -- a redirect resolves to its target -- and shows
// its parameter fields.
_selectCategoryTemplate: function(entry) {
// Left as the plain label (not "label → target") -- putting the arrow in the input's own value
// would get re-used as the search query if the box is focused again to pick something else.
this._categoryTemplateInput.val(entry.label);
this._categoryTemplateName = entry.target;
this._jederSection.toggle(true);
return this._renderCategoryTemplateFields(entry.target);
},
// One text input per TemplateData param of `name`, into the shared "Malûmatên çavkaniyê" grid, or a
// lone "Rûpel" box when the template has no TemplateData.
_renderCategoryTemplateFields: function(name) {
var self = this;
this._jederFieldsFieldset.empty();
this._jederFieldWidgets = {};
this._categoryTemplateLangParamKey = null;
return cite.templateParams(name).then(function(params) {
// A param labeled "koda zimanî" is filled from the language dropdown: found before filtering, so
// it is left out of the fields and emitted by _doSubmit.
(params || []).forEach(function(p) {
if ((p.label || "").trim().toLowerCase() === CATEGORY_TEMPLATE_LANG_LABEL) {
self._categoryTemplateLangParamKey = p.key;
}
});
var filtered = (params || []).filter(function(p) {
return CATEGORY_TEMPLATE_COVERED_PARAMS.indexOf(p.key) === -1 && p.key !== self._categoryTemplateLangParamKey;
});
var list = filtered.length ? filtered : [{ key: "rûpel", label: "Rûpel", required: false }];
list.forEach(function(p) {
// Reuse the jêder-* field's own width and multiline when the key matches one (cite.fieldSize).
var sizeHint = cite.fieldSize(p.key);
var widget = (sizeHint && sizeHint.multiline)
? new OO.ui.MultilineTextInputWidget({ rows: 2, autosize: false })
: new OO.ui.TextInputWidget();
self._jederFieldWidgets[p.key] = widget;
widget.on("change", function() {
self._updateReadyHint();
});
var layout = new OO.ui.FieldLayout(widget, {
label: p.label,
align: "top",
classes: ["add-example-field-span-" + (sizeHint ? sizeHint.span : 4)],
});
self._jederFieldsFieldset.append(layout.$element);
});
self._updateReadyHint();
initManualResize(self._jederFieldsFieldset); // matters now that a matched key like "sernav" can render multiline
});
},
// Fetches data via wikt.citoid, picks the matching jêder-* template, and fills its empty fields.
_fillFromCitoid: function() {
var self = this;
var query = this._sourceURLInput.getValue().trim();
if (!query) {
mw.notify(msg.citoidNeedsUrl);
return;
}
this._citoidButton.setDisabled(true);
this._loadingImage.toggle(true);
cite.fetchFromCitoid(query).then(function(result) {
// Citoid may pick the type only while none is chosen; a hand-picked one is kept.
var wasAutoDetect = self._currentJederKey === "citoid";
if (wasAutoDetect) {
var menu = self._sourceTypeDropdown.getMenu();
var item = menu.findItemFromData(result.jederKey);
if (item) {
menu.chooseItem(item); // this calls _setSourceType(jederKey) automatically
}
}
var targetJederKey = self._currentJederKey;
// Citoid shaped its fields for the type it detected; re-derive them when the active template
// differs.
var fields = targetJederKey === result.jederKey ? result.fields
: cite.citoidFieldsFor(targetJederKey, result.raw, query);
Object.keys(fields).forEach(function(key) {
var widget = self._jederFieldWidgets[key];
if (widget && !widget.getValue()) {
widget.setValue(fields[key]);
}
});
self._citoidSuccessMsg.show();
self._updateReadyHint();
mw.notify("Malûmat ji Citoid hatin barkirin.");
}, function(errorMsg) {
// Citoid cannot fetch some sites (x.com), so fall back to jêder-websîte with a guessed name.
var fallbackSite = wikt.citoid.websiteNameForUrl(query);
if (fallbackSite) {
if (self._currentJederKey !== "jêder-websîte") {
var menu = self._sourceTypeDropdown.getMenu();
var item = menu.findItemFromData("jêder-websîte");
if (item) {
menu.chooseItem(item); // this calls _setSourceType("jêder-websîte") automatically
}
}
var websiteWidget = self._jederFieldWidgets["websîte"];
if (websiteWidget && !websiteWidget.getValue()) {
websiteWidget.setValue(fallbackSite);
}
mw.notify("Citoid nikare vê malperê bixwîne; şablona Websîte hate hilbijartin, ji kerema xwe mayî bi destan tijî bike.");
return;
}
mw.notify(typeof errorMsg === "string" ? errorMsg : "Nikare malûmatan ji Citoid bistîne. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function() {
self._citoidButton.setDisabled(false);
self._loadingImage.toggle(false);
});
},
// Shows or hides the form and restores any saved draft. The title takes the button's place while
// open; the button stays put for when it closes.
isVisible: function() {
return this._frame.isVisible();
},
setVisible: function(visible) {
this._$button.toggle(!visible);
this._$title.toggle(visible);
this._frame.toggle(visible);
// Closing from the form's own "Betal bike" has to take the row away too, or the empty <li>
// it lives in is left behind as a bullet with nothing after it.
if (!visible && typeof this.onHide === "function") {
this.onHide();
}
if (visible) {
// Says what the button will actually do: publish now, or join whoever has taken the save
// over. Set here rather than at build time, since the owner may install itself later.
this._applyButton.setLabel(wikt.addExamples.submitHandler ? msg.add : "Qeyd bike");
alignFormToPage(this._frame.$element);
// A draft belongs to a new example; an edited one opens with what its line says.
var draft = this._editing ? {} : (draftStorage.Get() || {});
if (!this._textInput.getValue() && draft.text) {
this._textInput.setValue(draft.text);
}
if (!this._sourceInput.getValue() && draft.source) {
this._sourceInput.setValue(draft.source);
}
if (!this._sourceURLInput.getValue() && draft.sourceURL) {
this._sourceURLInput.setValue(draft.sourceURL);
}
if (!this._translationInput.getValue() && draft.translation) {
this._translationInput.setValue(draft.translation);
}
if (!this._transcriptionInput.getValue() && draft.transcription) {
this._transcriptionInput.setValue(draft.transcription);
}
}
},
// Opens the form on the index-th example of this sense. drawnText is what the page shows for it: the
// line found at that place has to say the same before anything from it is put in the form.
beginEdit: function(index, drawnText) {
var self = this;
this.clear();
this._editing = { index: index, original: null, example: null, key: null };
if (this._addTitle === null) {
this._addTitle = this._$title.text();
}
this._$title.text("Vê nimûneyê diguherînî.");
this._frame.$element.prepend(this._$title);
this._deleteButton.toggle(true);
this.setVisible(true);
this._applyButton.setDisabled(true);
this._deleteButton.setDisabled(true);
this._loadingImage.toggle(true);
var sectionKeyParts = this._definitionLevel[0].split("|");
api.get({
action: "query",
prop: "revisions",
titles: mw.config.get("wgPageName"),
rvprop: "content",
rvslots: "main",
formatversion: 2
}).then(function(data) {
var editing = self._editing;
if (!editing || editing.index !== index || editing.original !== null) {
return; // closed, or opened on another example, while this was loading
}
var content = data.query.pages[0].revisions[0].slots.main.content;
// The page as it will read once the queued changes are saved, so an example already edited
// in the queue is found as it is drawn.
if (typeof wikt.addExamples.pendingContent === "function") {
content = wikt.addExamples.pendingContent(content);
}
var loc = wt.locateSection(content, self._language, sectionKeyParts[0], parseInt(sectionKeyParts[1], 10));
var def = wt.findDefinitionLine(loc.lines, loc.posLineIndex, self._definitionLevel);
var block = cite.exampleBlocks(loc.lines, def).filter(function(b) {
return b.example;
})[index];
if (!block || !cite.exampleMatches(drawnText, block.example)) {
throw new edit.util.SubmitAbort("Ev nimûne di wîkîtextê de nehat dîtin — rûpelê nû bike û dîsa biceribîne.");
}
editing.original = block.text;
editing.example = block.example;
return self._fill(block.example).then(function(aliasMap) {
editing.aliases = aliasMap;
editing.key = self._currentJederKey;
self._syncTranslationBox();
self._deleteButton.setDisabled(false);
self._loadingImage.toggle(false);
});
}).catch(function(err) {
self._loadingImage.toggle(false);
mw.notify(err instanceof edit.util.SubmitAbort ? err.message : "Nikare muhtewaya heyî bixwîne.", { type: "error" });
self.setVisible(false);
self.endEdit();
});
},
// Leaves edit mode: the next time the form opens, it adds a new example again.
endEdit: function() {
if (!this._editing) {
return;
}
this._editing = null;
this._deleteButton.toggle(false);
if (this._addTitle !== null) {
this._$title.text(this._addTitle);
}
this._$titleHome.append(this._$title);
this.clear();
},
// Puts a line read by cite.parseCitationLine() into the form. A plain italic line and every
// {{nimûne}} redirect read as {{nimûne}}, so saving writes them as that. Resolves once the fields
// that depend on the template's own parameters -- a Kategorî:Çavkanî template's boxes, an alias
// such as "r=" for "rûpel=" -- are drawn and filled.
_fill: function(example) {
var self = this;
var key = example.mode === "jeder" ? example.template
: example.mode === "category" ? CATEGORY_TEMPLATE_KEY
: example.mode === "raw" ? RAW_TEMPLATE_KEY
: "";
this._sourceTypeDropdown.getMenu().selectItemByData(key);
this._setSourceType(key);
this._sourceInput.setValue(example.source);
this._sourceURLInput.setValue(example.sourceURL);
this._transcriptionInput.setValue(example.transcription);
var drawn = example.mode === "category"
? this._selectCategoryTemplate({ label: example.template, target: example.template })
: $.Deferred().resolve().promise();
var aliasTemplate = example.mode === "jeder" || example.mode === "category" ? example.template
: example.mode === "nimune" ? "nimûne"
: example.mode === "source" ? "jêder"
: null;
var aliases = aliasTemplate ? cite.templateAliases(aliasTemplate) : $.Deferred().resolve({}).promise();
return $.when(drawn, aliases).then(function(_, aliasMap) {
cite.applyAliases(example, aliasMap || {}, Object.keys(self._jederFieldWidgets));
// buildCitation() writes the language parameter itself.
var langKey = self._categoryTemplateLangParamKey;
if (langKey) {
example.extra = example.extra.filter(function(param) {
return $.trim(param.split("=")[0]) !== langKey;
});
}
self._textInput.setValue(example.text);
self._translationInput.setValue(example.translation);
Object.keys(example.fields).forEach(function(field) {
self._jederFieldWidgets[field].setValue(example.fields[field]);
});
// A box the form leaves out for this language still opens when the line already has something in it.
[[example.translation, self._translationInputLayout], [example.transcription, self._transcriptionInputLayout]]
.forEach(function(pair) {
if (pair[0] && !$.contains(self._$topLeft[0], pair[1].$element[0])) {
self._$topLeft.append(pair[1].$element);
}
});
// Kept for the save, which puts the rebuilt line's parameters back in the page's order.
return aliasMap || {};
});
},
// Removes the example being edited, through the same hand-over as a save.
_submitDelete: function() {
var self = this;
function settle() {
self._deleteButton.setDisabled(false);
self._applyButton.setDisabled(!self._textInput.getValue());
}
this._applyButton.setDisabled(true);
this._deleteButton.setDisabled(true);
this._sendReplacement(null, "−nimûneya bikaranînê bi amûra « {0} » (v{1}).".format(NAME, VERSION), settle, function(message) {
mw.notify(message || msg.saveFailed, { type: "error" });
settle();
});
},
// Replaces the example being edited with code, or removes it when code is null: queued with
// DefSideBoxes when it has taken the save over, published at once otherwise.
_sendReplacement: function(code, summary, reenable, error) {
var self = this;
var editing = this._editing;
var parts = this._definitionLevel[0].split("|");
var language = this._language;
var definitionLevel = this._definitionLevel;
function transform(revision) {
return {
text: cite.replaceExample(revision.content, language, parts[0], parseInt(parts[1], 10),
definitionLevel, editing.index, editing.original, code),
summary: summary
};
}
var handOver = wikt.addExamples.submitHandler;
if (typeof handOver === "function") {
try {
handOver({
transform: transform,
summary: summary,
removes: code === null,
// No key of its own: an example edited twice is two entries, applied in turn, the
// second reading the line as the first left it.
done: function() {
self.setVisible(false);
self.endEdit();
reenable();
},
fail: function(message) {
error(message || "Nikare li rêza guhartinan zêde bike.");
}
});
} catch (e) {
error("Nikare li rêza guhartinan zêde bike: " + (e && e.message ? e.message : e));
}
return;
}
api.edit(mw.config.get("wgPageName"), transform).then(function() {
location.reload();
}, function(codeOrAbort, result) {
error(codeOrAbort instanceof edit.util.SubmitAbort ? codeOrAbort.message
: msg.saveFailed + apiErrorMessage(codeOrAbort, result));
});
},
// Drops the saved draft so it cannot resurface on another page.
_clearDraft: function() {
draftStorage.Set({});
},
// Clears all fields in this form.
clear: function() {
this._textInput.setValue("");
this._sourceInput.setValue("");
this._sourceURLInput.setValue("");
this._translationInput.setValue("");
this._transcriptionInput.setValue("");
this._citoidSuccessMsg.hide();
this._sourceTypeDropdown.getMenu().selectItemByData("citoid");
this._setSourceType("citoid");
},
// Warns when a Citoid URL was typed but never fetched, then defers to _doSubmit. A name that
// cannot be bolded is reported under the text box instead, never blocking.
submit: function() {
if (this._citoidPending() && !window.confirm("Te lînk, DOI an ISBN nivîsand lê hîn jê malûmat nehatine standin. Bêyî wan berdewam bikî?")) {
return;
}
this._doSubmit();
},
// Generates and submits the wikicode, then inserts the resulting HTML element if no errors occurred.
_doSubmit: function() {
var self = this;
self._textInput.setDisabled(true);
self._sourceInput.setDisabled(true);
self._sourceURLInput.setDisabled(true);
self._translationInput.setDisabled(true);
self._transcriptionInput.setDisabled(true);
self._sourceTypeDropdown.setDisabled(true);
Object.keys(self._jederFieldWidgets).forEach(function(key) {
self._jederFieldWidgets[key].setDisabled(true);
});
// self._disableTranslationChk.setDisabled(true);
this._applyButton.setDisabled(true);
this._clearButton.setDisabled(true);
this._loadingImage.toggle(true);
var source = this._sourceInput.getValue().trim();
var sourceURL = this._sourceURLInput.getValue().trim();
var text = this._textInput.getValue().trim();
// The filled-in fields of the selected template. buildCitation() adds the language parameter and
// the "Ç:" flag itself.
var jederFields = {};
var usingCategoryTemplate = this._currentJederKey === CATEGORY_TEMPLATE_KEY && !!this._categoryTemplateName;
if (usingCategoryTemplate) {
Object.keys(self._jederFieldWidgets).forEach(function(key) {
// A raw linebreak would break the single-line "#*" wikitext just like in the example text,
// but these are plain param values (not display text), so collapse to a space, not <br>.
var value = nlToSpace(self._jederFieldWidgets[key].getValue().trim());
if (value) {
jederFields[key] = value;
}
});
} else if (this._currentJederKey && jederDefs[this._currentJederKey]) {
jederDefs[this._currentJederKey].fields.forEach(function(field) {
var widget = self._jederFieldWidgets[field.key];
var value = widget ? nlToSpace(widget.getValue().trim()) : "";
if (value) {
jederFields[field.key] = value;
}
});
cite.withLinkAsUrl(this._currentJederKey, jederFields, sourceURL);
}
// A Kategorî:Çavkanî template is used as soon as it's picked, even with every field left blank
// (still submittable as a bare jêgirtin=); a jêder-* type only counts once a field is actually filled.
var usingJederTemplate = usingCategoryTemplate || (this._currentJederKey && Object.keys(jederFields).length > 0);
var usingRawTemplate = this._currentJederKey === RAW_TEMPLATE_KEY;
var minakAnNa, sablon;
if (usingRawTemplate) {
// A citation, so "#*" like the others -- the line itself is built below, not assembled here.
minakAnNa = "*";
sablon = "";
} else if (usingJederTemplate) {
// The jêder-*/Kategorî:Çavkanî template itself is used as the template, not as the "2=" content.
minakAnNa = "*";
sablon = usingCategoryTemplate ? this._categoryTemplateName : this._currentJederKey;
} else if (source || sourceURL) {
minakAnNa = "*";
sablon = "jêder";
} else {
minakAnNa = ":";
sablon = "nimûne";
}
var listMarker = "".padStart(this._definitionLevel.length - 1, "#") + minakAnNa;
var title = mw.config.get('wgTitle');
// One builder for every citation line here and in the wizard. Bolding the entry name, the language
// parameter, the "Ç:" flag and <br> conversion all belong to it.
var code = cite.buildCitation({
mode: usingRawTemplate ? "raw"
: usingCategoryTemplate ? "category"
: usingJederTemplate ? "jeder"
: "plain",
marker: listMarker,
language: this._language,
title: title,
template: usingCategoryTemplate ? this._categoryTemplateName : this._currentJederKey,
fields: jederFields,
langParamKey: this._categoryTemplateLangParamKey,
quote: text,
source: source,
sourceURL: sourceURL,
translation: this._translationInput.getValue().trim(),
transcription: this._transcriptionInput.getValue().trim(),
// An edited line keeps its translation, and every parameter the form has no box for as long
// as it is still the same kind of line.
keepTranslation: !!this._editing,
extra: this._editing && this._editing.key === this._currentJederKey ? this._editing.example.extra : []
});
// definitionLevel[0] is "type|ordinal": the section's position among same-type headings within its
// language, resolved client-side, never MediaWiki's page-wide DOM id number.
var sectionKeyParts = this._definitionLevel[0].split("|");
var sectionType = sectionKeyParts[0];
var sectionOrdinal = parseInt(sectionKeyParts[1], 10);
if (this._editing) {
// A citation written out by hand is saved exactly as typed, its list marker included.
// Otherwise the rebuilt line keeps the page's own parameter order.
var replacement = usingRawTemplate
? (/^#/.test(text) ? text : this._editing.original.match(/^#+[:*]/)[0] + " " + text)
: cite.keepOrder(code, this._editing.original, this._editing.aliases);
this._sendReplacement(replacement, "±nimûneya bikaranînê bi amûra « {0} » (v{1}).".format(NAME, VERSION), reenable, error);
return;
}
// Read by the success handler below, which is a sibling of the transform, not nested in it.
var emptyTemplate = false;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = edit.util.SubmitAbort;
// api.edit() fetches the revision it will conflict-check against and hands it here, so the
// insertion runs on exactly that content. Named, so another gadget can run it instead.
function buildEdit(revision) {
var pageContent = revision.content;
// Said here rather than left to the locator: which sections this tool does not work in is
// this tool's own business, and the locator only knows that the key names no section.
if (!sections.headingNamesFor(sectionType)) {
throw new SubmitAbort('Navê beşê xelet e. Di beşên "Ji wêjêyê" û "Ji wêjêya klasîk" ev amûr nayê bikaranîn.');
}
// loc.lines covers the rest of the page from this language's heading on: the saved page is rebuilt
// from it below (see "text:").
var loc = wt.locateSection(pageContent, self._language, sectionType, sectionOrdinal);
var langSectionIndex = loc.langSectionIndex;
var lines = loc.lines;
var targetLineIndex = wt.findDefinitionLine(lines, loc.posLineIndex, self._definitionLevel);
// Where to insert inside this definition's "#:"/"#*" block. findBlockEnd() keeps multi-line
// templates whole; exampleTemplateNames tells a real example from an annotation.
var insertionFound = false;
var lastRelevantBlockEnd = -1;
var isJederFamily = (sablon === "jêder" || Object.prototype.hasOwnProperty.call(jederDefs, sablon));
var scanIndex = targetLineIndex + 1;
// Read off the definition line, so a sub-sense's examples are matched at its own depth and
// a deeper "#" line ends the block instead of being skipped past.
var annotation = annotationRegex(lines[targetLineIndex]);
var citation = citationRegex(lines[targetLineIndex]);
for (; scanIndex < lines.length; scanIndex++) {
var scanLine = lines[scanIndex];
if (!annotation.test(scanLine)) {
// Left this definition's own block (a sub-sense, a new definition, a new section).
break;
}
var blockEnd = findBlockEnd(lines, scanIndex);
var templateName = getLineTemplateName(scanLine);
var isExampleLine = templateName !== null &&
exampleTemplateNames.indexOf(templateName) !== -1;
if (isJederFamily) {
// A jêder-family example goes after every "#:"/"#*" line of this definition.
lastRelevantBlockEnd = blockEnd;
insertionFound = true;
} else if (sablon === "nimûne") {
// "nimûne" examples go before any "#*" line, but after "#:" lines.
if (citation.test(scanLine)) {
break;
}
lastRelevantBlockEnd = blockEnd;
insertionFound = true;
}
scanIndex = blockEnd; // Skip past the whole (possibly multi-line) block
}
var insertAt = lastRelevantBlockEnd >= 0 ? lastRelevantBlockEnd + 1 : scanIndex;
// After the relevant block, or straight after the definition when it had no "#:"/"#*" lines.
lines.splice(insertAt, 0, code);
// The "no example yet" marker goes once a real one lands. The whole "#:"/"#*" block is swept, not
// just its last line: the marker is often followed by other annotations.
var sweepFrom = targetLineIndex + 1;
for (var sweep = sweepFrom; sweep < lines.length; sweep++) {
if (!annotation.test(lines[sweep])) {
break;
}
if (EMPTY_EXAMPLE_MARKER.test(lines[sweep])) {
lines.splice(sweep, 1);
emptyTemplate = true; // tells the DOM update below to replace the marker's <li>
sweep--;
}
}
return {
text: pageContent.slice(0, langSectionIndex) + lines.join("\n"),
summary: "+nimûneya bikaranînê bi amûra « {0} » (v{1}).".format(NAME, VERSION),
};
}
// DefSideBoxes takes the save over when its panel opened this form: the edit joins that gadget's
// queue instead of publishing now.
var handOver = wikt.addExamples.submitHandler;
if (typeof handOver === "function") {
try {
handOver({
transform: buildEdit,
// The line on its own, for a caller that has nowhere to splice it yet: a sense still
// in someone's queue has no line number to be found by (DefSideBoxes' new-sense row).
line: code,
summary: "+nimûneya bikaranînê bi amûra « {0} » (v{1}).".format(NAME, VERSION),
done: function() {
self.setVisible(false);
self.clear();
self._clearDraft();
reenable();
},
fail: function(message) {
error(message || "Nikare li rêza guhartinan zêde bike.");
}
});
} catch (e) {
error("Nikare li rêza guhartinan zêde bike: " + (e && e.message ? e.message : e));
}
return;
}
api.edit(mw.config.get("wgPageName"), buildEdit).then(function() {
api.parse(code).done(function(data) {
var $renderedExample = $(data).find("ul > li").html();
var $item;
// Insert rendered example into page
if (emptyTemplate) {
self._$lastExample.html($renderedExample);
$item = self._$lastExample;
} else {
self._$lastExample.after($item = $("<li>").append($renderedExample));
}
$item.css("background-color", "lightgreen");
setTimeout(function() {
$item.css("background-color", "inherit");
}, 1000);
self._$lastExample = $item;
});
self.setVisible(false);
self.clear();
self._clearDraft();
reenable();
}, function(codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
error(codeOrAbort.message);
} else {
error(msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
function error(errorMessage) {
mw.notify(errorMessage || "Nimûne nehat weşandin, îhtimalen rûpel hate guhartin. Ji kerema xwe re rûpelê dîsa bar bike û dîsa biceribîne.");
// A draft is kept for a new example only: an edited one would come back on the next new one.
if (!self._editing) {
draftStorage.Set({
text: self._textInput.getValue(),
source: self._sourceInput.getValue(),
sourceURL: self._sourceURLInput.getValue(),
translation: self._translationInput.getValue(),
transcription: self._transcriptionInput.getValue(),
});
}
reenable();
}
function reenable() {
self._textInput.setDisabled(false);
self._sourceInput.setDisabled(false);
self._sourceURLInput.setDisabled(false);
self._translationInput.setDisabled(false);
self._transcriptionInput.setDisabled(false);
self._sourceTypeDropdown.setDisabled(false);
Object.keys(self._jederFieldWidgets).forEach(function(key) {
self._jederFieldWidgets[key].setDisabled(false);
});
// self._disableTranslationChk.setDisabled(false);
self._applyButton.setDisabled(false);
self._cancelButton.setDisabled(false);
self._clearButton.setDisabled(false);
self._loadingImage.toggle(false);
}
},
};
});
// </nowiki>
iy1plkvwdsgejpamr4s7a14ap5a9gnx
Bikarhêner:Wikihez/Gadget-wikt.add-examples.css
2
1357894
6534967
6513859
2026-09-20T18:48:12Z
Wikihez
7344
Test loader: run the local add-examples too, so the queue sees its removal flag
6534967
css
text/css
/* <nowiki> */
/* Design tokens shared with the "Entry wizard" redesign, so gadgets read as one site: warm parchment card
palette, Spectral/IBM Plex Sans/IBM Plex Mono type, no drop shadows, pill-shaped action buttons. */
@import url('https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,300;0,400;0,600;1,400&family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,600;1,400&family=IBM+Plex+Mono:wght@400;500&display=swap');
:root {
--wikt-ae-font-sans: 'IBM Plex Sans', system-ui, sans-serif;
--wikt-ae-font-mono: 'IBM Plex Mono', monospace;
--wikt-ae-card-bg: #fffdf8;
--wikt-ae-card-border: #e6dfd2;
--wikt-ae-divider: #efe8db;
--wikt-ae-input-border: #cfc7b8;
--wikt-ae-input-bg: #fff;
--wikt-ae-text: #1c1a17;
--wikt-ae-text-body: #4a453d;
--wikt-ae-text-label: #3d3830;
--wikt-ae-text-muted: #8a8378;
--wikt-ae-text-placeholder: #b3ab9e;
--wikt-ae-accent: #2a5db0;
--wikt-ae-accent-hover: #1c4685;
--wikt-ae-accent-bg: #eef2fa;
--wikt-ae-accent-border: #d5def0;
--wikt-ae-destructive: #9c3a28;
--wikt-ae-section-bg: #fbf8f1;
}
html.skin-theme-clientpref-night {
--wikt-ae-card-bg: var(--background-color-base, #101418);
--wikt-ae-card-border: rgba(255, 255, 255, 0.08);
--wikt-ae-divider: rgba(255, 255, 255, 0.06);
--wikt-ae-input-border: rgba(255, 255, 255, 0.2);
--wikt-ae-input-bg: rgba(255, 255, 255, 0.04);
--wikt-ae-text: #f0ece4;
--wikt-ae-text-body: #cfc7b8;
--wikt-ae-text-label: #e4ddd0;
--wikt-ae-text-muted: #a89f8f;
--wikt-ae-text-placeholder: #6b6459;
--wikt-ae-accent: #6ea1e0;
--wikt-ae-accent-hover: #8fb6e8;
--wikt-ae-accent-bg: rgba(110, 161, 224, 0.15);
--wikt-ae-accent-border: rgba(110, 161, 224, 0.35);
--wikt-ae-destructive: #d97a63;
--wikt-ae-section-bg: rgba(255, 255, 255, 0.03);
}
.add-example-form .oo-ui-inputWidget-input {
font-family: var(--wikt-ae-font-sans) !important;
}
.add-example-form .add-example-fieldset {
margin-bottom: 1em;
}
/* Form panel: card with border and radius, no shadow (matches the Entry wizard "no shadows" rule) */
.add-example-form {
width: 100%;
box-sizing: border-box;
margin-top: 0.5em;
padding: 1.25em;
font-family: var(--wikt-ae-font-sans);
color: var(--wikt-ae-text);
border: 1px solid var(--wikt-ae-card-border);
border-radius: 14px;
background-color: var(--wikt-ae-card-bg);
}
/* Title shown in place of the "add example" link while its form is open */
.add-example-form-title {
font-family: var(--wikt-ae-font-sans);
font-weight: 600;
font-size: 13.5px;
color: var(--wikt-ae-text-label);
}
/* While an example is being edited the title heads the form itself, which opens under that example. */
.add-example-form > .add-example-form-title {
display: block;
margin-bottom: 0.75em;
}
/* Field labels: consistent weight/size/color across all OOUI FieldLayouts in the form */
.add-example-form .oo-ui-fieldLayout-header label {
font-family: var(--wikt-ae-font-sans);
font-size: 12.5px;
font-weight: 500;
color: var(--wikt-ae-text-label);
}
/* Text inputs: shared border/radius/background, focus ring instead of OOUI's default */
.add-example-form .oo-ui-textInputWidget input,
.add-example-form .oo-ui-textInputWidget textarea {
border-color: var(--wikt-ae-input-border) !important;
border-radius: 10px !important;
background-color: var(--wikt-ae-input-bg) !important;
}
.add-example-form .oo-ui-textInputWidget input:focus,
.add-example-form .oo-ui-textInputWidget textarea:focus {
border-color: var(--wikt-ae-accent) !important;
box-shadow: 0 0 0 3px var(--wikt-ae-accent-bg) !important;
}
.add-example-form .oo-ui-textInputWidget input {
min-height: 44px;
}
/* Action buttons: pill-shaped, accent-colored to match the Entry wizard's Bişîne/secondary buttons */
.add-example-form .oo-ui-buttonElement-framed > .oo-ui-buttonElement-button {
border-radius: 999px !important;
}
.add-example-form .oo-ui-flaggedElement-progressive.oo-ui-flaggedElement-primary .oo-ui-buttonElement-button {
background-color: var(--wikt-ae-accent) !important;
border-color: var(--wikt-ae-accent) !important;
}
.add-example-form .oo-ui-flaggedElement-progressive.oo-ui-flaggedElement-primary:hover .oo-ui-buttonElement-button {
background-color: var(--wikt-ae-accent-hover) !important;
border-color: var(--wikt-ae-accent-hover) !important;
}
.add-example-form .oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive:not(.oo-ui-flaggedElement-primary) .oo-ui-buttonElement-button {
background-color: var(--wikt-ae-accent-bg) !important;
border-color: var(--wikt-ae-accent-border) !important;
color: var(--wikt-ae-accent) !important;
}
.add-example-form .oo-ui-flaggedElement-destructive .oo-ui-buttonElement-button {
color: var(--wikt-ae-destructive) !important;
}
/* Two-column top section: example text left, source type/link right; collapses to one column below 640px */
.add-example-top-grid {
display: grid;
grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
gap: 16px 20px;
align-items: start;
}
.add-example-top-col {
display: flex;
flex-direction: column;
gap: 12px;
min-width: 0; /* lets long unbreakable text shrink instead of overflowing */
}
/* URL input and Citoid fetch button, each on its own line */
.add-example-url-row {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 8px;
}
.add-example-url-row .oo-ui-textInputWidget {
width: 100%;
}
.add-example-url-row .add-example-citoid-fetch {
align-self: flex-start;
}
.add-example-citoid-success {
font-size: 0.92em;
color: var(--wikt-palette-green6, #14866d);
}
/* Source-info section: "Malûmatên çavkaniyê" header + the jêder-* fields grid */
.add-example-jeder-section {
border-top: 1px solid var(--wikt-ae-divider);
background-color: var(--wikt-ae-section-bg);
padding: 1em 1.25em;
margin: 0.75em -1.25em 0; /* bleeds tint to the panel's side edges only */
}
.add-example-jeder-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 0.6em;
}
/* Section eyebrow style, matching the Entry wizard's small-caps section labels */
.add-example-jeder-header-label {
font-family: var(--wikt-ae-font-mono);
font-size: 11px;
letter-spacing: 0.12em;
color: var(--wikt-ae-text-muted);
}
.add-example-jeder-header .add-example-citoid-clear {
font-size: 12px;
}
.add-example-jeder-header .add-example-citoid-clear .oo-ui-iconElement-icon {
min-width: 14px;
min-height: 14px;
background-size: 14px;
}
/* jêder-* template fields: 12-column grid, each field spans its own jederDefs[key].span columns */
.add-example-jeder-fields {
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
gap: 4px 12px;
align-items: start;
}
.add-example-jeder-fields .oo-ui-fieldLayout {
min-width: 0; /* lets a field shrink below its content's natural width */
margin-top: 0 !important; /* overrides OOUI's own stacking margin, not wanted between fields sharing a grid row */
}
.add-example-field-span-1 { grid-column: span 1; }
.add-example-field-span-2 { grid-column: span 2; }
.add-example-field-span-3 { grid-column: span 3; }
.add-example-field-span-4 { grid-column: span 4; }
.add-example-field-span-5 { grid-column: span 5; }
.add-example-field-span-6 { grid-column: span 6; }
.add-example-field-span-12 { grid-column: span 12; }
.add-example-jeder-fields .oo-ui-fieldLayout-header label {
font-size: 0.95em;
}
.add-example-jeder-fields .oo-ui-textInputWidget {
max-width: 100%;
width: 100%;
font-size: 0.95em;
}
.add-example-jeder-fields .oo-ui-textInputWidget input,
.add-example-jeder-fields .oo-ui-textInputWidget textarea {
font-size: 0.95em;
padding: 4px 8px;
min-height: 2.2em;
line-height: 1.4;
resize: vertical;
}
/* Kategorî:Çavkanî template search box (search/filter interaction ported from Wiki/start.js's own "__category__"
picker) -- sits in the top-right column directly under the source-type dropdown, not in the jêder-fields grid;
its picked template's own fields append into .add-example-jeder-fields (see above) like any other template. */
.add-example-cat-tpl-search-row {
position: relative; /* anchors .add-example-cat-tpl-results directly under the input */
}
/* Marks a redirect entry with where it actually resolves to (see showResults() in the JS) */
/* "Verify the template" link -- opens the template's own wiki page in a new tab, same 🔗 affordance
Wiki/start.js/util.js use in their own searchable dropdowns (see tplPageUrl in the JS). */
/* Larger font for the top-section textareas than the bibliographic sub-fields below */
.add-example-top-col textarea {
font-size: 1.05em;
}
/* Bottom action row: Biweşîne + hint, spacer, Paqij bike, Betal bike */
.add-example-main-buttons {
display: flex;
align-items: center;
margin-top: 0.75em;
padding-top: 0.75em;
border-top: 1px solid var(--wikt-ae-divider);
gap: 0.5em;
flex-wrap: wrap; /* keep buttons from overflowing on narrow screens */
}
.add-example-ready-hint {
font-family: var(--wikt-ae-font-sans);
font-size: 12.5px;
color: var(--wikt-ae-text-muted);
}
/* The line under the example box: the bold-marks button, then what will happen to the entry's own name. */
.add-example-text-tools {
display: flex;
align-items: center;
gap: 0.5em;
margin-top: 0.35em;
}
.add-example-bold-btn {
flex: 0 0 auto;
padding: 2px 7px;
font-family: var(--wikt-ae-font-mono, monospace);
font-size: 12.5px;
line-height: 1.3;
color: var(--wikt-ae-text);
background: var(--wikt-ae-input-bg);
border: 1px solid var(--wikt-ae-input-border);
border-radius: 4px;
cursor: pointer;
}
.add-example-bold-btn:hover {
border-color: var(--wikt-ae-accent, #36c);
}
.add-example-title-bold-hint {
font-family: var(--wikt-ae-font-sans);
font-size: 12.5px;
color: var(--wikt-ae-text-muted);
}
.add-example-title-bold-hint.add-example-title-bold-warn {
color: var(--wikt-ae-destructive);
}
.add-example-buttons-spacer {
flex: 1 1 auto;
}
/* CSS-only loading spinner, matches dark/light mode */
.add-example-spinner {
display: inline-block;
width: 1.2em;
height: 1.2em;
vertical-align: middle;
border: 2px solid var(--wikt-ae-input-border);
border-top-color: var(--wikt-ae-accent);
border-radius: 50%;
animation: add-example-spin 0.8s linear infinite;
}
@keyframes add-example-spin {
to { transform: rotate(360deg); }
}
.add-example-loading-label {
margin-left: 0.5em;
color: var(--wikt-ae-text-muted);
}
/* A reader who asked for less motion gets MediaWiki's own reset, which cuts every animation to 0.01ms
-- the ring cannot turn there, so it stops pretending to and reads as a mark beside the label. */
@media (prefers-reduced-motion: reduce) {
.add-example-spinner {
border-style: dashed;
}
}
/* Narrow screens: collapse the top grid to one column */
@media (max-width: 640px) {
.add-example-top-grid {
grid-template-columns: 1fr;
}
}
/* Small screens: tighter padding, jêder-fields grid drops to 2 columns */
@media (max-width: 480px) {
.add-example-form {
padding: 0.75em;
}
.add-example-jeder-section {
margin-left: -0.75em;
margin-right: -0.75em;
}
.add-example-jeder-fields {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.add-example-jeder-fields [class*="add-example-field-span-"] {
grid-column: span 1;
}
.add-example-jeder-fields .add-example-field-span-12 {
grid-column: span 2;
}
}
/* Custom drag-to-resize grip for textareas (see makeManualResizable in the JS, ported from Wiki/start.js) --
Safari has no native corner resize handle on textareas at all, this is the fix for that. */
.add-example-resize-grip {
position: absolute;
right: 2px;
bottom: 2px;
width: 14px;
height: 14px;
cursor: ns-resize;
touch-action: none;
opacity: 0.5;
z-index: 2;
background-image: linear-gradient(135deg, transparent 0 50%, var(--wikt-ae-text-muted) 50% 100%);
background-repeat: no-repeat;
}
.add-example-resize-grip:hover,
.add-example-resize-grip:active {
opacity: 0.9;
}
/* Warns when the example/translation/transcription text has a literal linebreak */
.add-example-form textarea.add-example-has-linebreak {
border-color: var(--wikt-palette-yellow6, #ac6600) !important;
}
/* Backdrop mirrors the textarea's text with a marker at each linebreak; metrics synced from the textarea via JS */
.add-example-linebreak-backdrop {
position: absolute;
inset: 0;
z-index: 0;
margin: 0;
overflow: hidden;
white-space: pre-wrap;
word-wrap: break-word;
color: transparent;
background: transparent !important;
border-color: transparent !important;
box-shadow: none !important;
resize: none !important;
pointer-events: none;
display: none;
}
/* Textarea sits above its backdrop with a transparent background so the marker shows through */
.add-example-form textarea.add-example-has-linebreak-source {
position: relative;
z-index: 1;
background-color: transparent !important;
}
/* The ↵ marker itself, placed after each line followed by a linebreak */
.add-example-linebreak-mark {
background-color: var(--wikt-palette-yellow6, #ac6600);
color: var(--wikt-palette-white, #fff);
border-radius: 2px;
}
/* Dark mode for the automatic theme, which follows the device. */
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os {
--wikt-ae-card-bg: var(--background-color-base, #101418);
--wikt-ae-card-border: rgba(255, 255, 255, 0.08);
--wikt-ae-divider: rgba(255, 255, 255, 0.06);
--wikt-ae-input-border: rgba(255, 255, 255, 0.2);
--wikt-ae-input-bg: rgba(255, 255, 255, 0.04);
--wikt-ae-text: #f0ece4;
--wikt-ae-text-body: #cfc7b8;
--wikt-ae-text-label: #e4ddd0;
--wikt-ae-text-muted: #a89f8f;
--wikt-ae-text-placeholder: #6b6459;
--wikt-ae-accent: #6ea1e0;
--wikt-ae-accent-hover: #8fb6e8;
--wikt-ae-accent-bg: rgba(110, 161, 224, 0.15);
--wikt-ae-accent-border: rgba(110, 161, 224, 0.35);
--wikt-ae-destructive: #d97a63;
--wikt-ae-section-bg: rgba(255, 255, 255, 0.03);
}
}
/* </nowiki> */
7i4o8gtudpmbo46itmhiuh4d1atfkwm
Bikarhêner:Balyozbot/kontrol/rêzkirina beşan
2
1358302
6534910
6534757
2026-09-20T14:41:06Z
Balyozbot
7867
[[Special:PermanentLink/6298023|Bot]]: +1 xeta
6534910
wikitext
text/x-wiki
== Nenas beş ==
* [[dinê]]: nenas beş "Formekê navdêrê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[di ... re]]: nenas beş "Bazinedaçek" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[da]]: nenas beş "Teşeyeke alîkar" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[xew neketin çavên (yekî)]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[tengî]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[telp]]: nenas beş "Navder" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[pelîd]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[nihanî]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[dînî]]: nenas beş "2. dîn, bêhiş, şêtî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[dînî]]: nenas beş "3. roj" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[welê]]: nenas beş "2. lê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[welê]]: nenas beş "3. gazîkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[xeyr]]: nenas beş "1. ne-" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[xwînrêj]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[xemrevîn]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[dused]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[cewşen]]: nenas beş "binêre" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kûz kirin]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[dîn û har]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[polîsxane]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hengil]]: nenas beş "Agadarî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[double]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[abrunden]]: nenas beş "Tewandin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[شق]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[befolgen]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[meşhûd]]: nenas beş "Etîmolojî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[şehmoz]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[sîl]]: nenas beş "2. lêdana bi destî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[çelçelok]]: nenas beş "Her wiha" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[şelek]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ebrû]]: nenas beş "1. mûyên li hindavî çavan" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[seriketin]]: nenas beş "Ji kurdî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[şihgirtî]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kortek]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[qediyye]]: nenas beş "Ji wêjeyê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[pêguherî]]: nenas beş "Ji kurdî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mehma]]: nenas beş "Bidin ber" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[jiyarpesêr]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[mirovgir]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Behr qufle ye, dar mifte ye, nêçîrvan çû neçîrê, nêçîrê neçîrvan xwar]]: nenas beş "Bersiv" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[destûrname]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bandêr]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bêemsal]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[camax]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bêgêwil]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[cabil]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[beytu]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bêqedr]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[dilnizm]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[dûvelang]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ewlemend]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[diltenik]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[pêlte]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[debax]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[dilgerm]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bêhempa]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ejê]]: nenas beş "Kinî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[gelek]]: nenas beş "Diyarker" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[heb-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Desmal]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nîbar]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[lam]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Daner]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kat]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[tak]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[DNA]]: nenas beş "Kurte" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hine]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[aynî]]: nenas beş "2. wek hev" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[serxwe]]: nenas beş "Rengder" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hendik]]: nenas beş "her wuha" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hewran]]: nenas beş "Binêre herwiha" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[qede]]: nenas beş "3. perdaq, piyale, glas, şûşe" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ornag]]: nenas beş "Çavkani" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-anî-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[nivîser]]: nenas beş "Bi soranî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[nîvek]]: nenas beş "2. yek nav" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ceme]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-ar-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dinê]]: nenas beş "[[dinê]]" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[helî]]: nenas beş "2. ji "helîn"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-ariya-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-arî-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[helî]]: nenas beş "2. ji "helîn"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-aşêr-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-aşart-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[nîroj]]: nenas beş "2. nîva rojê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bêje]]: nenas beş "Formek lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[cîns]]: nenas beş "3. cûnek pantolonan" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ferd]]: nenas beş "2. kîs, tûr" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kêlîn]]: nenas beş "Nadêr" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kêlandin]]: nenas beş "Nadêr" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dermana]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aso]]: nenas beş "Bikaranîn" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-avêj-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[venda]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[firar]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[istem]]: nenas beş "2. amûra agir pê têt hilkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[weste]]: nenas beş "2. pispor, zana" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[nîşa]]: nenas beş "2. karbohîdratek" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ejî]]: nenas beş "Kinî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[biran]]: nenas beş "2. ji "bira"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[xêr]]: nenas beş "3. fûriyayî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[nêv]]: nenas beş "3. 50%" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[baqole]]: nenas beş "Binêre herwiha" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hel]]: nenas beş "4. axir" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bilik]]: nenas beş "[[bil]]" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[lexem]]: nenas beş "2. bernav, paşnav" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[tewiran]]: nenas beş "2. nêzî "tewirandin"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[tews]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[tama]]: nenas beş "2. ji "tam"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[armîş]]: nenas beş "[[armûş]]" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[zehr]]: nenas beş "2. ziman yan zarava" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[zehir]]: nenas beş "2. ziman yan zarava" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[nifsî]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[tewiran]]: nenas beş "2. nêzî "tewirandin"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-qoqilîn-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-pirs-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-nêr-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-nivîsîn-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-kuj-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-kir-]]: nenas beş "Rehekî lêkerê 1" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-kir-]]: nenas beş "Rehekî lêkerê 2" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-kev-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-ket-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-ken-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-k-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[palî]]: nenas beş "Ji "pal"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-hat-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[tihnî]]: nenas beş "[[tên]] (1 û 2)" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-daliqîn-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[tîhin]]: nenas beş "[[tên]] (1 û 2)" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-da-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[têhin]]: nenas beş "[[tên]] (1 û 2)" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-bîn-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-bêj-]]: nenas beş "Reheke lêkerê 1" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-bişkurîn-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-bir-]]: nenas beş "Rehekî lêkerê 1" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-bir-]]: nenas beş "Rehekî lêkerê 2" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-bijî-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-betilîn-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-be-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-bar-]]: nenas beş "Rehekî lêkerê 1" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-b-]]: nenas beş "Rehekî lêkerê 1" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-b-]]: nenas beş "Rehekî lêkerê 2" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-şîn-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-şewitîn-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-çû-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-çikîn-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[nizim]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-ç-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-za-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-xist-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-x-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[-west-]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[xweger]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[zere]]: nenas beş "2. parçeya zer ya hêkê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[xeram]]: nenas beş "2. ne helal" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hîker]]: nenas beş "2. ya / yê ku mirovî hî dike" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hêker]]: nenas beş "2. ya / yê ku hêkan dike" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tûran]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[fîlan]]: nenas beş "2. gelek "fîl"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ferî]]: nenas beş "3. ya / yê feran" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[neqaş]]: nenas beş "Navder" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[cf.]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[cf]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[renî]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[at large]]: nenas beş "Îfadeya pêşdaçekî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[asane]]: nenas beş "2. hêsan e" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[vize]]: nenas beş "2. enerjî, hêza ku kar pê tê kirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[pêşber]]: nenas beş "Etîmolojî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hewramanî]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Îranê]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[doskî]]: nenas beş "Werger" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bird]]: nenas beş "Nadêr" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[fihêl]]: nenas beş "2. helal" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[telk]]: nenas beş "2. xefik, davik" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[zavê]]: nenas beş "Forma navdêrê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[leqan]]: nenas beş "2. nesekinîn" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[tixar]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hena]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ebrû]]: nenas beş "2. namûs, heya, şeref" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bêtilî]]: nenas beş "2. nebûna tiliyan" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ewlekarî]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ayinî]]: nenas beş "2. wek hev" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[zem]]: nenas beş "Nadêr" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[vîna]]: nenas beş "2. ji "vîn"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tirkiya]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[cahde]]: nenas beş "2. xebat, kar" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[w]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Erjengê]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[آموز]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[xwiyanî]]: nenas beş "2. ne biyanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[zaaf]]: nenas beş "2. zeîfî, qelsî, kêmasî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[raper]]: nenas beş "2. yê ku radiperre" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ـێـ]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[qadî]]: nenas beş "2. ji "qad"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[خوان]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[xweyanî]]: nenas beş "2. ne biyanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[جو]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bişar]]: nenas beş "Bernav" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[niqîn]]: nenas beş "2. ketin bin avê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[feraq]]: nenas beş "2. rex, alî, hêl" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[سو]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[کامل]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[daye]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[عرب]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[derbestî]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[eca]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[azir]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[مرد]]: nenas beş "1. مێهت / mêhit" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[مرد]]: nenas beş "2. نهقولهبوو / nequlebû" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[مرد]]: nenas beş "3. حولیكر / hulîkir" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[کمبخت]]: nenas beş "Etîmolojî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[cewan]]: nenas beş "2. pirjimariya peyva "cew"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[tenikî]]: nenas beş "Mînak" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hêlîna]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kenik]]: nenas beş "werger" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[xasok]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[qiçik]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[cinîk]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[niga]]: nenas beş "2. ji "nig"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[çarsî]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[çarpîne]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[çarpê]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[satin]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[devtî]]: nenas beş "Reşwendî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hermînê]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[topiz]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[zirtek]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[qestî]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[stûrî]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[piçpiç]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[şanişîn]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[sinetkirî]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rîsî]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rikdar]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[lalê]]: nenas beş "Herêm" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[jîrane]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Îranek]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ehmedî]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[sîresan]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dermanê]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[şindî]]: nenas beş "Etîmolojî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Babîlê]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Îrana]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[êdin]]: nenas beş "2.wate" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kurşe]]: nenas beş "Têkilî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[mergaset]]: nenas beş "Jji" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kurşe]]: nenas beş "Têkildar" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alano]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Iraqan]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Iraqek]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Firatê]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Iraqên]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Îraneke]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Îranekê]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[کوڕ]]: nenas beş "Dijmane" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[NY]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[EU]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[salve]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[مۆر]]: nenas beş "Rendêr" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[biryan]]: nenas beş "4. Rengdêr" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hizir kirin]]: nenas beş "= Etîmolojî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[tama]]: nenas beş "2. ji "tam"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[نام]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[takip]]: nenas beş "Etîmolojî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[akşam]]: nenas beş "Wextê nimêjan" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ziya]]: nenas beş "Etîmolojî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ریال]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[gumil]]: nenas beş "Pend" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[libin]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kart]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[badana]]: nenas beş "Zazakî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kim]]: nenas beş "4. bikim" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keywan]]: nenas beş "Sernav 2" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kamox]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[partir]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[katık]]: nenas beş "Zazakî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bedêl]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[teskin]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rang]]: nenas beş "Binêre herwiha" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Plane]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ihn]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ihre]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Scheu]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[nebelig]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ganze]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Granat]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ahnen]]: nenas beş "Tewandin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[abwehren]]: nenas beş "Tewandin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[abscheiden]]: nenas beş "Tewandin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ablenken]]: nenas beş "Tewandin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruhe]]: nenas beş "Bibîne" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Helfer]]: nenas beş "Bibîne" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[duschen]]: nenas beş "Tewandin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[eilend]]: nenas beş "Partîsîp I" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Luft]]: nenas beş "Çêkirina peyvan" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nachname]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Franse]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[leider]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tunnel]]: nenas beş "Dijmane" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[agieren]]: nenas beş "Tewandin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[seine]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[lutschen]]: nenas beş "Tewandin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Luftzug]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[erstes]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nigger]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[mehlig]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[lehmig]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[verrieben]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Monteur]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[geborsten]]: nenas beş "Partîsîp II" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Misere]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hervorgehend]]: nenas beş "Partîsîp I" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[oxidiert]]: nenas beş "Partîsîp II" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Psychiater]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bergûz]]: nenas beş "Bideber" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[abgehen]]: nenas beş "Tewandin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Liems]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Imels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lepes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leeps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hormans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rohmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kovals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tarts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Croces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Istres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reists]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[groan]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[opt]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rase]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tews]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Holtes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tholes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Naults]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[lenger]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Teps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neves]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Partens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Refs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neagles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hingles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[reap]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rife]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seldens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cerdas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pous]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ehlerts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hertels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dants]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cottens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Semraus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rakes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kears]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marescas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Terminis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[wand]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cures]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crespins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[pea]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[palî]]: nenas beş "Ji "pal"" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[pêşveçûyî]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rutans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[vase]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Veas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[seal]]: nenas beş "Lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nears]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[dealer]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoots]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Htoos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ramics]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[steel]]: nenas beş "Etîmolojî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Teels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hakes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harpes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Negris]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Greins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Noriegas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Diehls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leaths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kiln]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ceas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Landas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cheas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chaes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rustle]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[binder]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ponders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[sue]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mounces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rempes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rohes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baurs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Puertas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Burdas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brauds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tiendas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eids]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fehrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roanes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rothes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peaces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elvirs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kwons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Teters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[huts]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Does]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rinse]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Theos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Paeks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Altoms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gaitans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Warnes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Emerts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cutrones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Counters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kikers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yants]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rectitude]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stempiens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tingles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Etlings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Easons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leafs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rivets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[wonder]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Guses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ideas]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cospers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crespos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lious]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pasions]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Myries]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Perrinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cotas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rattis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Guras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[silo]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fehls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Veales]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[leaves]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reaumes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Choes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loverdes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Secords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ladies]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ideals]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kujê lêvê]]: nenas beş "Binere" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pitres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Varleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laverys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Levas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Veals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Capels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Places]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Posners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rascos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Selahedîn]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Teals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[froth]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spades]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fortes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Forets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tripis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yaws]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rahes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dutys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[dusty]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Areys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rayes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pelts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bairn]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Delrios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ngus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ungs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ngos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Houts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Huots]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Protos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Troops]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gartins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[baste]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maries]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mieras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[palm]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elsers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prestis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[laths]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Phus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hurs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[skill]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kills]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Danhs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dahns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wolfers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[freight]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leaheys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sayes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peeds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Piedras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sauers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sauders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ayes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cantys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Craces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cepedas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[lights]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Breits]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bertis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Herdmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Poons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Deons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Denos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tieus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sous]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roods]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[pair]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ulerys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ulreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruyles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruppes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sleepers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Koobs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gods]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sleemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Isreals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sailers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maisels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salemis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seeleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seelyes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bandas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sums]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Giens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[radius]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bolens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Streams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Amsters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hankes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reveres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[reverse]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rughs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[warp]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Givans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ashes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keeps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gerkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rieths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reiths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ehles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lorias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Solaris]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sailors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lehews]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Netts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[fires]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Noltes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tolens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Viruets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Virtues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tobons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[eel]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Astins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dyals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wahs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lingos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ligons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Karns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meches]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Veeders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cloers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Corles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Files]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[pengir]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[mamiz]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[slain]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lians]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[azerpîr]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cares]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Creas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Races]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ceras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tuffs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sgros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sorgs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[averês]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boulters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Toors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rapes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Almys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Malys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meolas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Manions]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Merendas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bomans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nambos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Amblers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marbles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brames]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Altos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Toals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Liconas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Monreals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Oldmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamonds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lermans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lyalls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lallys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Allies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Allgeiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keelans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fails]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tenaglias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Labarges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Galeas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lavines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Radles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Radels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alreds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamarres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keenas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alesis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Guarins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rieras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barillas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gilanis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ganos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ganoes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Genaos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Costigans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mangones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Teagans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ilagans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tagles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rastogis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gangis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gagers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gregas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Regas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Garicas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arcigas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gaals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Feras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fears]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hayters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heaters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hersim]]: nenas beş "Pîrepend" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bevards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reardons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Andas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dahans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Danleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yandles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laydens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aamodts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Damatos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nicolays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Catinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Atencios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Naces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Canes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cenas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neaces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saubers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Basyes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barredas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tobars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barots]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bators]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bardos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brodas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bordas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Obamas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Batens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Beairds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Blahas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Albees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Badals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baizas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Taboras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bacas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cabas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Braleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gabers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nabers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Berans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barbres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brennas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Knabs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brandaus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rabels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Albers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Labers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brakes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ziebas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baizes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rabins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Liebmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Belairs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Labries]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bariles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Linebacks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Labbes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zonas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loezas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lexas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maws]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wardas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Navias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Avinas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vanmeters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Avilas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alavis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ristaus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rideaus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Auls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kaus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gauers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Argues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gebauers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Treats]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Attars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Motas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aritas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Monterrosas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tesars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Essas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Desais]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Daises]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Castos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Raneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neyras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nearys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rayls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Larys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Voras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ravals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Umars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sinatras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Morantes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Martones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Amaros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Armors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Morras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marreros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Martels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tramels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lairs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alires]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lieras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Raias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gaylers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grauers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Groats]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Robares]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Berroas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Daras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Raads]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Plairs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tapps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosatis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sirotas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laroes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loeras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lavins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Antolins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marenos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Morenas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yonans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ronaynes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nielands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Annas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[shank]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mantias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Naglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rangels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Agners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Legans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Angels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nagles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dinardos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cohans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Conatsers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pestanas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Burnettes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brunets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Butners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buchners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brunis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Borths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Holberts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Borons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Borellis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bognars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Broadheads]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brides]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strebs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Belmores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eberharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sterbas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rybas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rabys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yarbers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bartlings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barhams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brahams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Branhams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chaberts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bacherts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bachers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bozas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bowsers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brouses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bourns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dubons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lobues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bettons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bonsus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shobes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bertinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grobs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[testis]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Beros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ساحل]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Borers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brodeurs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bonos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Broomes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Robledos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Benos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ebrons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Breons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bodners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bolsters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strobels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strobles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Belotes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bodis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tebos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bedoys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Badolatos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Beatos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strubles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Burls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Blows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bowlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wrobels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kolbes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lobbs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brindles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gilbreths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Helberts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bendels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Beldens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brendels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brendles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gobels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gobles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Balkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Beachlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Balzers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Blazers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brazels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stablers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kilbanes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bahls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baldes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bittners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Biros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gobins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eblings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Birdens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kiblers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bevilles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Botkins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dubiels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bevers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Battes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bylers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brieres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reibers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Breiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Branders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grebs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Robinettes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lembos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leberts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Beltres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Liebels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Belays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leybas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Erbes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reebs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Beemers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Embrees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bartees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bares]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Breas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Binegars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baughers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Banes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bames]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bermeas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baeks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abeds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Badys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yambaos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bartelts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barletts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Talberts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tarbets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abstons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bastons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bastards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barstads]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salibs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abshers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mebanes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Beemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Raybons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yebras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Taborns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tabrons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barrets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sabers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brases]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riberas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[gam]]: nenas beş "Formên alternatîv" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barrettes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ظالم]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nabors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rabons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barnos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bonars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boatners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bartones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cowherds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laroches]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pichons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kochers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clothiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hincks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chiems]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chesnutts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chestnuts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buchers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Michalecs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rasches]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aschers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Schaers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Charters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Charests]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cathers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chapples]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chappels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pelchats]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chinas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Canetes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Suraces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cartees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Centenos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chimentos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cosners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Screens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Casteels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ayscues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chilcoats]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Charettes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sacchettis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Castels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Casters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Caisons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marcheses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cravers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arcaros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[scrap]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Claros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Racines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Anciras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cartes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crears]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cerras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Craders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clayborns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Claibornes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Faraces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[scant]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Catrons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cantors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Canters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rances]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arcands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Canepas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Macraes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cameras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Malecs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lezcanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Langaricas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lacerdas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mancias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coccias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ceasars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bearces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cables]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Calebs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Braces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Broadys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Birneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brineys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ebys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Byes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Beys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bluster]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[obtuse]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bucherts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stubers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strubes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strubs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brunings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bruners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bureks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bukers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brughs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rubles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lubers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Burdens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buntens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Burkles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Golubs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gublers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bulgers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Burbages]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boucks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buckos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brunettes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buettners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eppards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Danglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Legrands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Glanders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Denas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dances]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Madsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Delas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dalkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kadels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Radkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cardons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Condras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Conards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ladas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marcys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cutters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Currents]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Curtners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clums]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Culls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buices]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buccis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cutchers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Suchers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crutes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Curets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Curds]]: nenas beş "Formeke serenav" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[cursed]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ferruccis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crochets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cerons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ciras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Secrests]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ceperos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cerratos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carretos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ceasers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Caziers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crases]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cranks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rancks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lackners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coveys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coverts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cervones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[conserve]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crouts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Curtos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crouses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cousers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lancours]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Courtneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gouchers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Costers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Encisos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Costes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[cloister]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cotners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lorencs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crooms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Corkers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coblers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alcocers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crapos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pecors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cotos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Connetts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Converses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gencos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gonces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cogans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coynes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yonces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Noces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cordons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chacons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Comperes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Campiones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Caltons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coriells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Collars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carolls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crokes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cerinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coiners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Choos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Condos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cadottes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dolces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coads]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pitcocks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Caitos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Calos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Caleros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Covels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coulters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Colts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clousers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Colens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elcocks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lincks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clinks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maricles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ciottis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trices]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ricottas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mencias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Castines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Redics]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cyphers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chums]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eagers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[grease]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zenders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aymonds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Derys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dryes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dadys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dusts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Studers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dumans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cudes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Suders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dupres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prudes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grondins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goodners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rodens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Doners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Drones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Birkelands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ridders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Radys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Drays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dyars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ringwalds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wingards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reinards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gordans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dorgans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cardones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Doneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sowders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Woodlands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rowdens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wordens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nedrows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mooradians]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Girods]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosados]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Delongs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gordens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gonders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moldens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lemonds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dolmas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sneads]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riedys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reidys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vidrines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vidas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gidneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ridens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Radins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Drains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nardis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Altidors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[doings]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Godins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Redicks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hodels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dyes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Doerrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dents]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Emonds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Edmistons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Diems]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Deems]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tiedemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mendietas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Diels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ledays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Delays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gudes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Friedels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fiedlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fielders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alderfers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cardiels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Daceys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cadles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Deharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aldingers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leaders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[leaders]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cedanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Canedos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eadys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bauders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Daubers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sheads]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shades]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sheards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Traders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Darters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Renards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lenards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nadlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Randles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dankerts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gardins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dares]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Redas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dears]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Raders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Magers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gambrels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Caniglias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gaiters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Agins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nagis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Graeffs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Faustins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Flurys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ferrys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fryers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Renfrows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Frisinas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Friars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ferebees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Morfins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fortmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Folmars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Korfs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Forests]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Foersters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cofers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Broadfoots]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wilfongs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wolfingers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leforts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Floros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lafortes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[lifts]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sutliffs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rifkins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seifrieds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Friers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Friels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reiffs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Friddles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fiddlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ferries]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Freires]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Frees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shareefs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fellers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leflers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Festas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Feenstras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rafters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Frases]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Frears]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ferras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rafiqs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laflers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fallers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ferias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Facers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Toyes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ewers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Viels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Deveras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Deavers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ehrets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stayers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Steers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sesays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Espers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Resters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reinosos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tregos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grotes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Terceros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reasers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Premos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Poches]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pliegos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Noles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lenos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Magners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mangers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ameens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Naeems]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rendes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cerones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[censored]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Creedons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Canteros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Temmes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mires]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meeses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meeces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Levers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Revels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lepores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eaglesons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lems]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leites]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eitels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[legs]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Edlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Belows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Blowes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lebows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nales]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tighes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heights]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Geters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Galentines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Feis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cohees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elseas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leases]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seales]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Garniers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Graniers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saeterns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reales]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gearins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reagins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gainers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laufers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[refusal]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Teagles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Legates]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zanghis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hardways]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Washers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harvies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hartes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hearts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Earths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tahers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hassels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hadlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Erdahls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heralds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Amoahs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hosmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sheans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shan]]: nenas beş "Navên wê yên din" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hangers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hestands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dahlens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hadens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harneds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hardens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stringhams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mahs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Perhams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lathems]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vahles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harvels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[lashes]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamothes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barthels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Halberts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rihas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hails]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Drohans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bahs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gruners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gums]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dugars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gaunas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gudgers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Duggers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gorneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Govers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gossers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grosses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gorins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Garons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rogans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ragons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ragones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grieps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riggans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sligers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Siglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gillers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kigers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Geres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Geers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gerens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Egners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Engleberts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bogers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grobes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grebes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gebres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saegers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seagers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gavers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Garvers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gravers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hartings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gandarillas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Radigans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Adgers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dragers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Groses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Largos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gorals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Egnors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Groens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gorens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Giraldos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lodges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Regos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Geros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Doigs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gaos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goads]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Godas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Monges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tangs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Magins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Glancys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Luongs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gulinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Guels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Voglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bolings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maglios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lengs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gamels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ragles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Larges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cargiles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Griders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Negrins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kings]]: nenas beş "Tevdeng" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gilders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Geibs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Taings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loughs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mergens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bilgers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Biglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Giblers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Genos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Doeges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goedes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tengs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Enges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gagniers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grinages]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gearings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Geans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zagers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gravelles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stagers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gassers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Megnas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Murgas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tragers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Argotes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Garrens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Graners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Raglands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bogars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bargos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gabors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gagners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gangers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fagens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fegans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Makars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Karams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alkires]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kopkas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kogans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Malaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dakes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Deaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Blakeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bobaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Joiners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Joyes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Najis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Jians]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mosiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leists]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Steils]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sindelars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Diops]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[interests]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Steinerts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stiners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eismans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ardoins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tiemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kenistons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rinkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ikners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kerins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kreins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kiners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nields]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Edlins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chiens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Annies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Palmieris]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mariottis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eiferts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riedls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[idiots]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rickes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riecks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Archeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoytes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stuhrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hunters]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Humans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kuhls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hues]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hucks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harvins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dohenys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wohlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[houses]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hours]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hounds]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hudons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Duhons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mathesons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stonehams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shoes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hosiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Protheros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Horns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rohns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Thorens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rhotens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hopes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hodos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gohns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Honeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roehms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Holdemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Holstines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mohls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Upholds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gohs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoangs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Henwoods]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chhetris]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dinhs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ihrkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ihdes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heids]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neatherys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rishers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Honers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moshiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Herns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hrens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rhems]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rehms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Routhiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rehders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Drehers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Behrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rhynes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Henries]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gheens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mehs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hems]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heplers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mehlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Holles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hegdes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hecklers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Haveys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[sheath]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Longos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gaglios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Golders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Deols]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Doles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ledos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Klocs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Evolas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hotalings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Floreas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Galindos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sitlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Steimles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Steimels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Estills]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pollicks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pinellis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Linkers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kerlins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lupiens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Linens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ladniers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eaglins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rimels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Milers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Imlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Millikens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Likes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kiles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lanings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Anglins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riggles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Siegals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Willefords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rickels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barils]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Biblers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Levins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[sleeve]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[letters]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lessers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Selks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elisons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peltons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Epplers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leppers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eplers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Liendos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elsens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Klavers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lechers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lerches]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sinegals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Searles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Easlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saelees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lapes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leaps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barfields]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[lays]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Raleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Layers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Earlys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rayles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Learys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[layers]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rawleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vreelands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lavenders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lindauers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Culhanes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[laughs]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aulds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dauls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dulas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Altices]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[satchel]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alessis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Singhals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Haslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lashers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Searls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Krals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Klars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lachers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Atilanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lindemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[psalm]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Palms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Parmleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Linams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trambles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laskas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kalins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lakins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[lakes]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kalers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cails]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loveladys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aldreds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ackleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lackeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tablers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bartles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bartels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bolars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Albors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Albros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bealls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[labels]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kielys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kuans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kuklas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kulkas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kulaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kalras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Komars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Knokes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Runks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keowns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[knots]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kronks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bonks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kevins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[knives]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Knitters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kisers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riskes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vaneks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kluges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kites]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nikkels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kindles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kindels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kids]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kidneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keehns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Henkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kahans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kingerys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kingreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keltys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kestlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[spike]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ankenys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ankneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[knees]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Enkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zakis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trasks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Agans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sumlins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rumers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brummels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hallums]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Womers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mundos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Motes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hersoms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moshers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gormans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Garmons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Merlos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Morels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Toomers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moorers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Morontas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Motens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marchiones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lomans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Almons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[salmon]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Domans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Modglins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mocks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[remiss]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Misers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mesidors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Imrans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maturinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mirons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Miners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Minders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Klims]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kimes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Smidts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crombies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Morets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tomers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Haltemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Semons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mineros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moniers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Merinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Romines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cremers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tremains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Monters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Menons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elamins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meegans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Milsteads]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lemas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Adems]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mezas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Amys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Raymos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mabeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rotmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Morants]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stimacs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Steadhams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sesmas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mansos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salamas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salaams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mayrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marletts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arments]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Liermans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grimaldos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tomasis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lanums]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lumans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ulmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Manors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Armons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[huntsman]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Huamans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Holemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mogans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gamons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gelmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mangals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lagmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wollams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mallows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Solimans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Speakmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Makers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Getmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lemars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lermas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Corams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carmons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cormans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Camarenas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Amars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Linseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Insleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nisleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Staleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lykes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sealys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tuels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lutes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lusks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rules]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Runkels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Runkles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leungs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Plums]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Plumers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rumples]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goslins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loops]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loopers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gholstons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rolens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lenoirs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Parsleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rapiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loperas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Karps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Praks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rapers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Padrons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Patons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Espinas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pesinas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sapiens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Palens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Espaillats]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Galpins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Palkos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Paiks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Atnips]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Parentis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ptaceks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Towles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lowers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loverings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Venos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stoutes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Guintos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Toures]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Souters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Soules]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Totos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tretos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Soes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Perinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pineros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Monroys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Romers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rolings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Troianos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Horaces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roaches]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Paternos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Perones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Glores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rogels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Donderos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lacours]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Escotos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cottles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Toccis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cattons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bourdons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Booes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Satos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tsaos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Paos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[soap]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruttens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nurses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Unsers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rundles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hannums]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tubmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dunkins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Enlows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lovens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vallones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vonas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Isenhours]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Patnodes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dapontes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nolettes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Contehs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tonches]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reitanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ensors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Morlans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Konos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tunisons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roddens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tiens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hignites]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gerlings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Feins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neices]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nieces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wesners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Senters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Greenos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Edelens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Enderles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Encks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buelnas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tenas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Venas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Naves]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vidanas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eastins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Creasons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ngans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ganns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mangins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eastmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Smetanas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saintils]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Irelans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Indas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nidas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Babars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cinques]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Quintelas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[squad]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pyes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shupes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruppels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grupes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Upps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Turpens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[spunk]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kupers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pryers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Petrinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pointers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pastores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Speirs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Poiriers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Princes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Priers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Petricks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cripes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trempers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Speisers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Perreaults]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Parises]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arispes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Poppes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Troupes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Puertos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Petitos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Thorpes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stroupes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prestos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stropes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sopers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Speros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lopers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Palmeros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Palmores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Perkos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Repkos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Topes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Potes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Plews]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Welps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Players]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[splatter]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Platters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lampsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Plantes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pilats]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Plaisances]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Plasencias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pinals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aplins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pitchers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Paits]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lapines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gilpins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Piels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lipes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peils]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Piles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kipers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peris]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hipshers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pahls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Repps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Petermanns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prietos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seipps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pimentals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Petermans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peelers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leepers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[speck]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Capens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alpers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Walps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Partons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pearts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pentas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prestias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sproats]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pestas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pasters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spinosas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spehars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slates]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slapes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kasers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gilcreases]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ushers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Husers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heusers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Blumers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bulmers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rumbles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fulfers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Uribes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aburtos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tarrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aylors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wronas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Guitrons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Durons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Latours]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fortiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rothers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tortoras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Perros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Foors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Foreros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rednours]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nolders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lordens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bortons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stromains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Camerons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marencos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rogues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rugose]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Birons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brions]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Serratos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rivenbarks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Risers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rippels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ripples]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Terrios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rinks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kirns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tringalis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Giltners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tinglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Griners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reils]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ilers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gribbins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bitars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bardins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Briands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Airds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Myhres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hymers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rohs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Widners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Silveras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Verrets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rotters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Testers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Renterias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Storers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rossiters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Isners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ploegers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Partees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Perreiras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lenarts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Greeners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Monteiros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Markees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reimans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Minears]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heislers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leifers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ertels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ertles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reginos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fugeres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ferrees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Credeurs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Burdges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rendas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dranes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Naders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clausers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Corters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Records]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Corders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kuebers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boteros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Berras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rabers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seranos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reasons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pereras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[smear]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Treloars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lasiters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dearings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Garvins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Raves]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ravers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vergaras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tarters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roats]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shears]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shares]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sharers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Iriartes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pardis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ransoms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nagras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ragans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Andrys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dormans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Damrons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rakers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fragales]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hardages]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dorames]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Damores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Madores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meadors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maderos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Radices]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ardilas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alarids]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sprouts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stroups]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spradlings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prosts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trospers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spittlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pennisis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Esplins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lipinskis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Siples]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seips]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spaeths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Speras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aspers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Espinals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spradlins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Persads]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Espadas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spoors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slovers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ariolas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aldersons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Espins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Resnicks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sernas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Snares]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nasers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ritsemas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reitsmas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Luses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Solers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sloops]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Singlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seidls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saylers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salvas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hasslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ansels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slanes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sandlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kniselys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Knisleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Suks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kists]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kirsts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kieslings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stakes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keasts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kasters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Taskers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stakers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sitters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strites]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[stairs]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stairs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sartis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riesers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Isers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kinsers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kisners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Silks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lisks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lasseignes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salingers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fisters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sieverts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Geises]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gieses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eslicks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sicklers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ericksens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Howsers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sohs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sohals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Selphs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hirsts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shires]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hisers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shiners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shakirs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grishams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hisels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hisles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shiffers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Herbsters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sherberts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shetlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salehis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spahrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sahms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mehans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ehmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trostles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stotlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stelters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stetlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Setsers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Transues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saeteurns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rowses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mersons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Serinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sandels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Santores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Estimes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reismans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Erismans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Selvas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lieskes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Celestins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sepes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Edmistens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stecs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nases]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seamsters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eliasons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cullisons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Scullions]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Croses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sorces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Secors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meschers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Scrantons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Callisons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Caslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sayres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Swareys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Swaynes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Swaneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arvisos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Savants]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Garsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Asmars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Samras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mascaros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marascos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mastins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gansers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sandstroms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Denmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dansies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Snaders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Samples]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Samanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Amasons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Masis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Simas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bosmas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slivas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nosals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Solans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Malsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Burchettes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arnetts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aldretes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tamuras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Muratas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strachans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stratmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tiernans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trammels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lammerts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tarins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tyos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bartows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sostres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tatros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rattos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sortos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sortinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Troches]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bartolos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Listons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tilsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tineos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nietos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cottams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tollers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kortes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Teos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Capotes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Costins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hietts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Uptains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lipperts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trippes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kinters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Litts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tillers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tickles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Knuths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Guths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Threats]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bothes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hinotes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hights]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kitsch]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weltes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lotters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Threets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lirettes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Certains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Margarets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Atteberys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peetes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Netters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Annetts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamperts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Letsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elstons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lecontes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamontes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Almontes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Teeds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Poteats]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Teamers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mateers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weasts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sweats]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Autons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kasts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stanards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lastras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gartmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gleatons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Parmeters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tolans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rotellas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Akters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nittas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Triolas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tailors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tacks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slyes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wisers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Swaims]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Westalls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sawins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wegmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Monsours]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sunseris]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Useltons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Situs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tsuis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Suits]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saguns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stuebers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seuberts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vauses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sumpters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marottas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strikes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Swarts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wertmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sartins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Restos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spotos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sotelos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stokers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kosters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stivers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rists]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kistners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tanskis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tserings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Terans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosetes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gests]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gersteins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laseters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sautters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hasletts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Testas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strates]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Satters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stalters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Statlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tsangs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stangs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sandts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mastens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Santillos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Quasts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mozos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rozas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Razos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rizks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Setzers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hintzes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zayeds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zimas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nazaires]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Guays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lowreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tuohys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lokeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riffeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leveys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yowells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Readys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ryans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Drayers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ardreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keaneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laffeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rayas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yaras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Winers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Warths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Niswongers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fowles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Flowes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Antwis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiants]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Winkles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gwins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[swine]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weils]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weidlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Redwines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weidners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wideners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Howles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wahlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lowders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dowlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Welchers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wires]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dwires]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Winbergs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Webres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Winegars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weingarts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wachters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Westras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rainwaters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[wasnt]]: nenas beş "Kurte" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kwans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Warnkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wanglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wanders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Walfords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wagers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Waids]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trovatos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vogls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Voisins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vidrios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[divisor]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Virnigs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Virgins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vigeants]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cavins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vialls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Villanis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Iversons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Varbles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vrabels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Levitans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Valentis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vereens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Verdons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Viens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vranas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Navars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vierras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lauvers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reutters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Auths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hauts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Turis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reuters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rueters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tauers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rudermans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gendreaus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clunes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[seagull]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kieus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dunagans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kytes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tyes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fetrows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wittes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Winsetts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wints]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Traweeks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shutes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lutters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Turpins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[strum]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[unrest]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Durants]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trumbles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Plutas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mustains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nitschkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hurtts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Surettes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Towlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Routts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tutors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roughts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pintors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Poitras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pothiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Torellis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gortons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[restless]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tesslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Patanes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cuberos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maranos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rehaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kandels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tiners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[umpire]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heits]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hites]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Duffies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[mandatory]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Browers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[blot]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bians]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nabis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Schaners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stepans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[operand]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Naus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mungias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[maple]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Matneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yetmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Edmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Amends]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Samas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[maul]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Capellis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pacellis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Robainas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dieps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[tandoor]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Duans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Proanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Adsits]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Altiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Platers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alperts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ripe]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laportas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[pastoral]]: nenas beş "Etîmolojî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[mineral]]: nenas beş "Jê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moretas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[kilometer]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bails]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tepes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Barberos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[dune]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[duly]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dowers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[drove]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[driven]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[redress]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[dreary]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[drake]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sedors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[doorway]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[dolly]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[disinterest]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Litherlands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seidners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Paseks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spakes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Husts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Giesens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reddins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[surname]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dovels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Legrandes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crarys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carrys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Conants]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dorans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nardos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ardons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Jourdans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Borahs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bizarre]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gusmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Partains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stohrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sessas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Asays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hanes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Haens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trainas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[repay]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[dread]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Racicots]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ritaccos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ascencios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zufelts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Houpts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[retard]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arndts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Troups]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Celis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Edgins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lightys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rhees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weases]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Raos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Claars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dahirs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rideouts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[calibre]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lewters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Welters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[hog]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[carp]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Antills]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Epleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[sleepy]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Perrottas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[twin]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ginters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tigners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[thesis]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sethis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heists]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[consult]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cultons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[besmirch]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[beret]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bonines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bellows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[belie]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wahabs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Labats]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[basalt]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[barb]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bainters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[balm]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[beak]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Luvianos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rauhs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Holters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arceos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[reproduce]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laughters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Treeces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[republish]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ingenitos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tolins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[awry]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[wanton]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[wade]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[waddle]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[scum]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rhues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Uhers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bleaus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Poulins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[runway]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[untried]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pannus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Autens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cauthens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Beaudins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buendias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[umlaut]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[saturnalia]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Draines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cousars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rabadans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Janaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[sanity]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nealys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tolands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lampings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mainas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hamills]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Patlans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[saloon]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Galeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bleas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baenas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kinmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mankins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kashanis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lokens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[keenly]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kosars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Srokas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tandys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Terreros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Levertons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lorees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rineys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roeders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Parliers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[repel]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roemers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[modeler]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Simerlys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[remake]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hilers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riehls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[relish]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gurules]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gerritys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[registered]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[regale]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[chosen]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[refute]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[redact]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[recipe]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[raze]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Braatens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rasp]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rasorial]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arriolas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rapine]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rankle]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moruas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[oracle]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Honores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[omen]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Drolets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seldons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ogre]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seddons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cuellos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[oboe]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pandeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Edingers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Uhls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Curcis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruccis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aceitunos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[worthless]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strehlows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[lithe]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rupes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Witters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Restainos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[chairman]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Markmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[shirk]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[elude]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[soot]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Capellos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mahans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hamans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chapins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bleak]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[coach]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[brothel]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[parcel]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[orchestra]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[nearby]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[coral]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leucks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Luecks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keirs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rikes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nieblas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Staubers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Straubes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Caricos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kulls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ectors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[fret]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[carbon]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nerios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[inland]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Redingtons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Edringtons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[roam]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elrods]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Koreas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Molders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Guls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leights]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Giblins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hutters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crolls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coops]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chisms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Guins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lacortes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Propers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pretes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Puhls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wynes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dashners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yagers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gareys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gearys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Campises]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Synders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reiders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rieders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chiangs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boulets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ainslies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[bonnet]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[blur]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bitlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Liberts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[blaze]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[blarney]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[blare]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hartins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hiners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lums]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Piners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Perins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Steurys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Suters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rune]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[rugged]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ruffle]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Solteros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[romp]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crookers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[robin]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Traores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rodartes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Suris]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ripple]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ringer]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ribbon]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rhodes]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rohdes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Islams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Smails]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salims]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Monteros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hinmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Warfords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Forwards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nerias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neiras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roedels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tacys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loredos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elahis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hailes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hannons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Haleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hilyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pichardos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Curtins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Byams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fehrmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kerbers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lecuyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Melniks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mathernes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Semans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neidigs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Luciers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cuffies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coiles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cielos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carpentiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bilellos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mabies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sturkies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Traviesos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sileos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Solies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cophers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Porches]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Supplees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peachers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sapersteins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiechmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hulses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Geisslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vaskes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hetzers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salamehs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goerings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Agredanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fitzers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sheers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Asencios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boscherts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bechtols]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mabees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laubers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baronis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arendts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Petteys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Menors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gatlins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shadles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Urendas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vescios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Korts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hittles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rigneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yingers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nowells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wevers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Truetts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nutts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trews]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dotens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arpins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Koops]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prattes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Placeks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tobers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moberlys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Daverns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dalals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cheynes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pechas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Norbys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cobos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Autins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Amees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alviras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alviars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Naumans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heimans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aungs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Guans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Naugles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vials]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Avellas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Breaults]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hajis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fortons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Notaros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lambies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Milliens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruehls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Uhlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Raabes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sabias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leamans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lainos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lahaies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nidays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Belsers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Belcastros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eatmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Amantes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Amentas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[signified]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ERW]]: nenas beş "Kurte" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Poyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alemus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sudans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Newsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Thiems]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Austons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Partins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tignors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Girtons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Plotkins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gliddens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Khas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cooters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Teaters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Quayes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Phams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hamps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hailus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trauts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tantons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Antleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Royles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shoups]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kippers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nighs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carlinis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Homas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rehmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ehrmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hamners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bahes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hawns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hauns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Haqs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kuhars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zehrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leedys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Erways]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wearys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sahas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peadens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Matars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alvardos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loomans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Molanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hayners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rameys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mahers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hamres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mehras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sianos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rocas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mouas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Munirs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lenons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Simes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mayons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kumars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mianos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maungs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lius]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Koehls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yelles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Glantons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yankes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Akeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Jennes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sicas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ahsans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ansahs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Graneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eglis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Geils]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fruges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sauters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Suteras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Liles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vallees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dionnes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nodines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tilneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Matins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Matsuis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ayons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Inoas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hanneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Noahs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hylers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hovans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hanans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hannas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lehanes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Haans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Danahys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Arnones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Naylors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Irions]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Snays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nieses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Masellis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Louders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Jases]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Biehls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seelys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Acords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chretiens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hanlys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Velazcos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Foyes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wolins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Inlows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Blivens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Belvins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Blasingames]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Balkos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Blakeleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bleakleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leibys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ibes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boices]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Liebs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gibbys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bozes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bechts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Basnets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gambers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bergerons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Benoits]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Biehns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Blakneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Beirnes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bondes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Beckels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Belyeas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tabones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Blasers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brasels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Balsers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bashams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bahams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bendas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brachts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rybaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bolanders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leivas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gaunts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Atkisons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Awads]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saads]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sadas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Haworths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Horwaths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Howarths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hagars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aubrys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bilals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ballis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ayads]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Adays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Novoas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nardones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alpaughs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lebarons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eakins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Farahs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Adkisons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Delanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Behars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baehrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boarmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Enloes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yadaos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Minayas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meaneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ledlows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dowells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hodgdons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pinedas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Doanes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yadons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Davinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Davilas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bandys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dalesandros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Badons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abdons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crowells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crosleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Croslands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rascons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Colyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Croleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lecroys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Corleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Croyles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rockeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crismans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riccos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Creswells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cayers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carmeans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Manceras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coulons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Comans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Corraos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rochins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coyners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Croneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coniglios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Catoes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Acetos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clasens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lackies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Richmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Schoens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Scheers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cashions]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Schianos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Catanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cataldos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cataldis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Casos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Casadys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Canhams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cocas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carwells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alcorns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loncars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leclairs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carellis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carliles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Calliers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carlens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Caninos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Liceas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cianos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Erbys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Breys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kubes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buhlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hublers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buchmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Burdines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bours]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Borcks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brisons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Breslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brasingtons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Brasfields]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harbers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bynoes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boumans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Koubas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stobers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bosters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abregos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bolers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Borels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Robels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bolsers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dhars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Homans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Athas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tahas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rigbys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grimleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riegers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Regiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Greelys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kogers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gehrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Haiglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grasers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grears]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ragers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoags]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goodells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Golsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Genslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slagers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Glascocks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Legares]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eargles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alegres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reagles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gilfords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sengers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Legers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rehrigs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Englers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sieglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gieslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seiglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Geislers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reigles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riegles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reigels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gasaways]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Magans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Higas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gaiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gondas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fichters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kifers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Frahers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fraires]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fromans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoefers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chelfs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Finchams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Feslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Freels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fites]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Feits]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peixotos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Veteres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Siecks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Perazas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reinkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riekens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nehers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Memons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rothwells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tonellis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lalones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Anellos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eilers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eggletons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ealeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ayeles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cahues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tardys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dartys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Duntons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dunmores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Duchenes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dorts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Durfeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kleimans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keilmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hilemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heilmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hamelins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lueras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lauers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Halbrooks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ahls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alcalas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Learns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Landmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lambertons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nailors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alcons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Karys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Krays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruhnkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riekers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Karners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kakars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Koteckis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Litkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sulaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Glinskis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kislings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kirchners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Halkos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ketners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kerleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kehrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kerchners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kaleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lakeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leikers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riekes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keelings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eakers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kanters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kasners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Askrens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maseks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sameks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cihaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Karlsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Johansons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Jasos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Liebls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Casianos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Allings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gillans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lilleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yohns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heyns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shueys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Humpherys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Houles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shelors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loehrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roehls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoyes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yohes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rothmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Thormans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Northams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hornbys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tholens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Holtens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Higdons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bohons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ihles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hiles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leshers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoseins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ehlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Berreths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lehners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Helens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ehlens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riehles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Whithams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hartfields]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hartons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prathers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Koplins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lipkas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Placencias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pinons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Penicks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Petrones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pardues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Puleos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Loupes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Raposas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yaples]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Parikhs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Repkas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pekars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Phairs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gowins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wingos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Levarios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alverios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Escanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Novelos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Licons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Locklins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nicotras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mutchlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hulmes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mosmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sammons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mosers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Momans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moyles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meloys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Morriseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Morrisettes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Manloves]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mixons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mikells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lemires]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Inmons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Minasians]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Imms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Melins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Michaliks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mensers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mesmers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Siemers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meisers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mesicks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Miceks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kmiecs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meleros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mollers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Miesners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Miseners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Himels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Emigs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meinkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cromleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maxons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mullanes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Umalis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Totmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Toumas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tomans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sarmentos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mayses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Samoras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Doumas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kellams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maiellos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buhrmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Colomas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Menards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Erdmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mardens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ramanis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Remaches]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Munars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nomuras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maroons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moranos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mankers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mankes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ekmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kamens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ellmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seamans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Suriels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lubys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dulins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lowdens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dowlens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lohs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lalondes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Longworths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kails]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pilons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nolins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Linleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yellins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lindleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lindors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ridlons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leightys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leveques]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Elsners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Selmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seligs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sadlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kaelins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mules]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meltzers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Semlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lemleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gillmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Malleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shulers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wilkisons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wolinskis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kliers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leeths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Theels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Melenas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Malarkeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Taltons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Antles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Blays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Albaughs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Knouses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rivkins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kipps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baiks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keltons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kelleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eckleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keeters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sisons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sonis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Isons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Silers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kinnisons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ringels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lingers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ringles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Livermans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Molins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Libermans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keifers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eicks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Uthes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hupps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Uhlmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rouths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Toths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bohrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kohns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Holls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lorahs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heffrons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hoffners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hivelys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rishels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Amrheins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hinkels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hinkles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hindles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heindls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hildens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heidlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bhatias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rohners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Herrons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Herrods]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kirchers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hentons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Donahues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Henks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kehns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Helveys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hellmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hendels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hearnes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hearons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dahers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ahearns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Whaleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hawleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Havers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ullahs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hatleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harvells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rahmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harkeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aherns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Haners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ahners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Haffners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Haydels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Healds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dahles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eylers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bodleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ewells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eversons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fuertes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pellers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zents]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Annes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Conines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lemmers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gleaves]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Etzlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ellers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lodens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Learneds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Koehns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Beiters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riebes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hagles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stegers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Streges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ficeks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fickes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Feicks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Deberrys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cleeks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eckels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tischers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stechers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bingers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bergins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gaithers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maestres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ealys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cullys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Covellis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Covilles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cofields]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chupps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Utechts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Richers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chopps]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cohns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rochons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mechlings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kelchners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Schislers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hickles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cheniers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Porchias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carberrys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cannells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Conlans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Calnans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Calises]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cieslas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heagys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vellas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nazaris]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Taillons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rennas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stagners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stangers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hagans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wandells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Doans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Donas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Landins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nilands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Danfords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Smales]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Smeals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eatmons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lavoies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Groleaus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aldanas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Attias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Andinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Huas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ackmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rubianos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Albos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Naabs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ryers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Turmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dutras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Byrums]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Troyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Royes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Waldrons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Worlands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Routons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Risos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sollers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rories]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lortons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roeslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gorders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rothrocks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reilys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tetricks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chirinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Renicks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kincers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Erlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heckers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Parkeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rineers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harfords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Narons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Randells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Legardas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lagardes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Krupas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kapurs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lupers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruples]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Petrovs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pruietts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Presnells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Preslers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pernells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pellots]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peltos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Topels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ransomes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Amburns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Branums]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Burnams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laveys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alveys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leavys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leyvas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Varelas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Valeras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Avelars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alvears]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Halpins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alphins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Almazans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Almanzas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Almands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Malands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nallys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Allyns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mallons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Allmons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Allgoods]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goodalls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Allains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lalanis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fraleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alfreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dayals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aldays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruybals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alburys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carolans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alarcons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Alaimos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Akthers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rathkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Khaters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hartkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Thakers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Akhters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ahlbergs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Halbergs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tegans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Agents]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Agrawals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Agarwals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Adus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Duas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Adragnas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gandaras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Daynes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Adneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abshires]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Berishas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abshiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sambranos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abramsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abids]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Diabs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aberles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bealers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abdous]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abouds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bodas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Abdos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Morgas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Magros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lanks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Balks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sandridges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Baades]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Capells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Celedons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Adeles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dewitts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leitners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Conders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Codners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Domkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Harbs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keerans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grenons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Negrons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trowers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trowells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rodwells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wixons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wislers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wimers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wittens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wickerts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ewerts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Swingles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weismans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wisemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weisingers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiesers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weatherbys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wasons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Valvos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Savells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vasallos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Aveys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Userys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Usreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ryus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eurys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ureys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tusings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gustins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Utseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Townleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weltons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lewtons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Triches]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ehrharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rhotons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Thornberrys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Theisens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hatems]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mehtas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mesters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tesmers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seiters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Teresis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eiserts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Powes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Winfords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Windles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Willdens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wyllies]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Willeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wanners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Waffords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wadleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vensons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Utters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Luries]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mumas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lukowskis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trieus]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Uries]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Duers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tryons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rickerts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reicherts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lowthers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strausers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Traubs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ratleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Towells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Towes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tonges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Toneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Firestones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tellos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tolles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lodatos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hitts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tingleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Timpsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tillsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tilleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tices]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wattersons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Testons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tretters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Poteets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Topetes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tiemanns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Steinhauers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stefaniks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Duttas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buteras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tathams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tarrys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tapleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stanfields]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slatens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stanburys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stanberrys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Antczaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Anctils]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maturos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tatmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tallmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hartles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hartels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Taggs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Squiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Portas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prengers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tarpleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pitzers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pirtles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tiplers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Piros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pinneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prindles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lipmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pillers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Keipers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pickards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pellmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pearmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peakes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lopatas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Parkmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pangles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pangs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Paffords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Izzos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hurtados]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Norens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Natolis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Doolans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lobos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Boecks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rydens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Downens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mellotts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Malos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Metzlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goldins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Golays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goyals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goodys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Godoys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Goldys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gloyds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ugaldes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Glaudes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Guintas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Giuntas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Guidices]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Giudices]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Magris]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Girmas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ginders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Redings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Igleharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gilreaths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lingles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gillens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gildners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grindles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hagins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ghanis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gersons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gonsers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Songers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gershons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shogrens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Regners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gerners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gerigs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gigers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Geraghtys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Haggertys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gellers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gehrets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hergets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gehmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ghanems]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gawels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wagles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gauses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maugers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gaumers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gatsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sontags]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gaminos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Manigos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Glavins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Galvins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grables]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gablers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Freyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Freyres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Frayres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Frayers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Frarys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fryars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lunsfords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Forslunds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lafons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Folans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lafonds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Folands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fogelmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Foglemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Flythes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fleishers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fishels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Finkels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Finkles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Friedls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fidlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fickles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fickels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fyes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Feys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Renfroes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ferrones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fitterers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ferrettis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Freers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ferres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fermans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Manfres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fernalds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ferlands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Telfers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Felters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Feldsteins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Steinfelds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Finemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Feinmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Feesers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Freeses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Freedmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Federmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Finazzos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fazzinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Favelas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lafaves]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Faths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Farneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Franeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Farners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Farrens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fairleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fraileys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fagers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Graefs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eysters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Steyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maynes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meanys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mayens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eymans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rivettes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Everitts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Everharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Estridges]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Selingers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eslingers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eshlemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eshelmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ervings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stepiens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Epsteins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pingels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eplings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Enseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Enrights]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ginthers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Enghs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hengs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Engelmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Englemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Engelharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Engleharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Heglands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Engdahls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Remicks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Emricks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ellwoods]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Woodells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eislers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Liesers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leisers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eisenhardts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eichlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reichels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reichles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hemanns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ehmanns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fogels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Egolfs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Fogles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Edisons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eidsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ebles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ebels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Easleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sealeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Relyeas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Earleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Eakles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leakes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rundells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Durnells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Draheims]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hermidas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Doucets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ducotes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strodes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Drostes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dosters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sodhis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Doshis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dorners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Norreds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Donlans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nolands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hadnots]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Donaths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lindos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dolins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Waddles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dewalds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Devers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Verdes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Depews]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Deegans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gandees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Waddys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dawdys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Davillas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Villadas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Daughterys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Daughtreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Garridos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Darrigos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Darleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Radleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Grandas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dargans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Daneks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Damerons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Medranos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Delaos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Daleos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Daigles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Gildeas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dahlkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Khaleds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dahlins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hilands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dagues]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cushmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Schumans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Curriers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Curreris]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cuylers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Curleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crossons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crossnos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cronins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rincons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nicastros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crisantos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Creasys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Searcys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cradys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Darcys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cothrens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cotherns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Torrences]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Correntes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Collums]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Culloms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cokleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lockeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cogers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Groces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cregos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cobians]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cobains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coalsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nolascos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clukeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Luckeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clowers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Corlews]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cloonans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Colonnas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Crevelings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clevingers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clareys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Clearys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chius]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chuis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cheevers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cheveres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chartiers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reicharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Hacklers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chalkers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chiassons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Chaissons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cellinis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cinellis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Conatys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Caytons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laucks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Caulks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cebulas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Caubles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cathys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tenesacas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cataneses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lacostes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Castelos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cascios]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Coscias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cartys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tracys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cartas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carrons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cranors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Craners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carneros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carrenos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carreons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carmonas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marcanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lorances]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carlones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Carlls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cralls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cragos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cogars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cargos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cansecos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saccones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pecinas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Canipes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Scagliones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cangelosis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Canadays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Dacanays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Camires]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ceramis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Jacomes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Camejos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Caitlyns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Cahalans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Calahans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Byards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Darbys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Butterys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buttreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Husebys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Busheys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shaubs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Bushas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Burians]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Urbinas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Kueblers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buerkles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Huebners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Buehners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Budnicks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salvinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slamas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sandinos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saindons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sahins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sinhas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sahnis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stuards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rustads]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruoffs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruffos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruvalcabas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rubalcavas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zollers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rozells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zoellers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rozelles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Royses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Soreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rowtons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tornows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rowseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Swoyers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sinors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riffles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riffels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reuthers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Theurers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reitzels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zeitlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riesters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reisters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Terriens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reinerts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Riellys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reillys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Serraos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reasors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Urenas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ruanes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rauens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rasors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sarros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rardins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rinards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Randols]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Roldans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Qadirs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Qadris]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spruells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pursells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prevos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Portelas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Plateros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Priores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pierros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pieronis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pineiros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Picenos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Picones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Phelans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Phalens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pettrys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Prettys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pittengers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pettingers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pinters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Petrins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pitners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Petitts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pettits]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Piettes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Petties]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Petites]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Persings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spigners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peiffers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pfeifers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peevys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peveys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Payes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peavys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Paveys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pardees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Peredas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Partlows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Paltrows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Padros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pardos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Novellis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Veillons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Snokes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Noseks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Senkos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reimanns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Niermans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tinchers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nichters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yungs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nguys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Newharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Warthens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Thens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nisbetts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nesbitts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neitzkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zienteks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Neimans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Niemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nazzaros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Razzanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Retanas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nateras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nasts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sants]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tsans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sanos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nasos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nasons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sanons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nappis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Papins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nakayamas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Yamanakas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Najars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rajans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Naitos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tianos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Niangs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Naings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Murpheys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Umphreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Murleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rumleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Smullens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Munsells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mowreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mowerys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wymores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tatoms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mottas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nimmos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Momins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mischkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Schimkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mittons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Minotts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sikkemas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mikeskas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Merrows]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mowrers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Merideths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Merediths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mentzers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Metzners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mendolas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Molendas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Milones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Melonis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Molines]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Melicks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mickles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mickels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meikles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mielkes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mehrotras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moreharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Medlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Melders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Medlens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mendels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Matutes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mattoons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Murthas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mathurs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Matsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mastons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Massons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sansoms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Misras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Masris]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seamons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Masones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mormans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marmons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Markels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Markles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marinellos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Romanellis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mazons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Manzos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Toothmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mantooths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mantheys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mathenys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mansurs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Surmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mansells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sellmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rymans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Manrys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Manharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rathmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mangats]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tamangs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mollas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mallos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maeses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meases]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maeders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maderes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Meaders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maradiagas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Madariagas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mabreys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maberys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lynams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Manlys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Longtins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Noltings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lohrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rohls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Silveys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Livseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sivleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lipsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pilsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lippmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pamplins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Nilsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Linsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Reindls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Linders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Redlins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lesages]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seagles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lepleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pelleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Longes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leongs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lenihans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Linehans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rendlemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lendermans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mingles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lemings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Remleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lemerys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lemaires]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Legeres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Legrees]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lefevers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lefevres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lefebres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lefebers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ledets]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Melanders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Ledermans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lecrones]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lorences]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leboeufs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lebouefs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lazos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lozas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Moylans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laymons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lawals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wallas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laverdes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Velardes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Leavells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lavelles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lavans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Navals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slatons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Latsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lasleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Salleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laplantes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Pallantes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lannis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Linans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lenhardts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Landreths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamores]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Merolas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tolmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamonts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Malkins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamkins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mayles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mealys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lameys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Maleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lemays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Mealers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lameres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tremblays]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lambertys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lambersons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rosenbalms]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lamarrs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Marlars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rallos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lalors]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Rajalas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lajaras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Larisons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lairsons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lanhams]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Lahmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Laforces]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zelenaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zelenkas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zafras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Zafars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Woolseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Woosleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Winklemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Winkelmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiselys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wilseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Witchers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wicherts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Winthers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Whitners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Winglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Werlings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wneks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wenks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiests]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weists]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiseners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weisners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiesners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weinmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiemanns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Winemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiemers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weimers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weilands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wielands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weikels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weikles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weigles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiegels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weigels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weigands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiegands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Widemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weidmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiedmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weidemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiedemans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weidemanns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiedemanns]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wickes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weicks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wiecks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weibels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weibles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wegners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wengers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Waneks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wankes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wignalls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wallings]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wardles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Walders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wakeleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weakleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weaklands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wakelands]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Whalens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wahlens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Veiths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vieths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Veigas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Varons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vantreases]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vallins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vancils]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Valcins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tuleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Utleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Truesdales]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wrotens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Towners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tsois]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tosis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Torains]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Torians]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trianos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Toppers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tolars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Torals]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Trinkles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tinklers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Theins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Thiens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tirrells]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Terrills]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Weyants]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tawneys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Taubs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tarpeys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Swilleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Willseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Swiharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wisharts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Swearengins]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Swearingens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Usmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sumans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Suiters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Urestis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Styers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Streys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Testermans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Streetmans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Treasters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Streaters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stormers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stromers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Strocks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Storcks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stokelys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stokleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stierwalts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stirewalts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stieglers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Striegels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stickels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stickles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Treeses]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Steeres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stracks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Starcks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stankes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Staneks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stalders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stadlers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spinellas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Spillanes]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Souzas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Suazos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sooters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Tesoros]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slowiks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Wolskis]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stehles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sleeths]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slivkas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slaviks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sippels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sipples]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shortens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Thorsens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shraders]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sherards]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sheleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sheelys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stahleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shatleys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shaheens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sheehans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shaeffers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sheaffers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shackelfords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Shacklefords]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Vesters]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Severts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stevers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stegners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sergents]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stengers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Veselys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Selveys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stelzers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seltzers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Smolens]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Selmons]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Snellers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sellners]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seiples]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seipels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Siebolds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seibolds]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seiberts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Stiebers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sieberts]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Siebers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seibers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slagels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Slagles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Segalls]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Siegles]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seigels]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seeligs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sladeks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sedlaks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Veaseys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Seaveys]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Schnecks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Schencks]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Schiebers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Scheibers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Scardinas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sciandras]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sciaccas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Scaccias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sathers]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sastres]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saravias]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Saraivas]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Safars]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sarafs]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Sandiegos]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[Solivans]]: nenas beş "Formeke serenavê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[viro]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[کار]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[کردی]]: nenas beş "Têkildar" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[فروش]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[دهان]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[بوس]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[بند]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[مال]]: nenas beş "2. خواركر / xwarkir" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[بال]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[خراب]]: nenas beş "Etîmolojî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[صفر]]: nenas beş "Hejmarnav" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[لیس]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[کیهان]]: nenas beş "Etîmolojî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[صعب]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[عصب]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[کنار]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[لیکن]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[لیک]]: nenas beş "Binihêre" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[روب]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[مات]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[میر]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ساق]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[تکان]]: nenas beş "Rehekî lêkerê" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[زلزال]]: nenas beş "Etîmolojî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[قدرة]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[هذب]]: nenas beş "2. لهزكر / lezkir" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[حريق]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[لوح]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[دعم]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[بني]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[حسنا]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[سجن]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ربيع]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[لحم]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[مسمار]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[ساعد]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[أصلي]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[فرشاة]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[كردي]]: nenas beş "Têkildar" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[سبح]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[عالج]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[سعال]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[جعل]]: nenas beş "2. زیرهڤان / zîrevan" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[عقل]]: nenas beş "2. لنگێت وی خواربوون / lingêt wî xwarbûn" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[قادر]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[بخير]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[صحي]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[یمك]]: nenas beş "Etîmolojî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[مقس]]: nenas beş "2. گهلهك ڕژت / gelek rijit" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[محرق]]: nenas beş "2. كوڕیت وی ل ناڤ ئێلێت دی دهلاڤبوون / kurrît wî li nav êlêt dî delavbûn" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[بلان]]: nenas beş "واتە" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[أرب]]: nenas beş "2. ههڕکی / herikî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[أبر]]: nenas beş "2. گهزت / gezt" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[نبه]]: nenas beş "2. هشیاركر / hişyarkir" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[الوبر]]: nenas beş "2. canewerekê zinaran" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[شرب]]: nenas beş "Bilêvkirin" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[tutirk]]: nenas beş "Formên alternatîv" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
* [[سنور]]: nenas beş "Çavkanî" (ne hate rêzkirin, ne pos_section e lê binbeş hene)
== Astengî di rêzkirinê de ==
* [[bariyer]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[bayist]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[bayist]]: astengî di rêzkirinê de ji ber binbeşa "Werger" (ne hate rêzkirin)
* [[biyang]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[biyokîmya]]: astengî di rêzkirinê de ji ber binbeşa "Têkildar" (ne hate rêzkirin)
* [[biyoteknolojî]]: astengî di rêzkirinê de ji ber binbeşa "Têkildar" (ne hate rêzkirin)
* [[botanîk]]: astengî di rêzkirinê de ji ber binbeşa "Têkildar" (ne hate rêzkirin)
* [[bêemrî]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[bêfermanî]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[bêjename]]: astengî di rêzkirinê de ji ber binbeşa "Hevmane" (ne hate rêzkirin)
* [[bêrîvan]]: astengî di rêzkirinê de ji ber binbeşa "Formên alternatîv" (ne hate rêzkirin)
* [[bûk]]: astengî di rêzkirinê de ji ber binbeşa "Têkildar" (ne hate rêzkirin)
* [[cilxane]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[derbûn]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[derkirin]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[dualîtî]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[gir]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[gir]]: astengî di rêzkirinê de ji ber binbeşa "Werger" (ne hate rêzkirin)
* [[gizing]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[hegemonî]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[heste]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[heywannasî]]: astengî di rêzkirinê de ji ber binbeşa "Têkildar" (ne hate rêzkirin)
* [[hezîran]]: astengî di rêzkirinê de ji ber binbeşa "Binêre herwiha" (ne hate rêzkirin)
* [[hiyeroglîf]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[jêkirin]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[kanûna paşîn]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[kefalet]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[komparatîv]]: astengî di rêzkirinê de ji ber binbeşa "Têkildar" (ne hate rêzkirin)
* [[kronolojî]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[melû]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[meriçîn]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[mexel]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[neqişandin]]: astengî di rêzkirinê de ji ber binbeşa "Bi alfabeyên din" (ne hate rêzkirin)
* [[nivistinxane]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[nîlûfer]]: astengî di rêzkirinê de ji ber binbeşa "Bi alfabeyên din" (ne hate rêzkirin)
* [[nîsanok]]: astengî di rêzkirinê de ji ber binbeşa "Navê zanistî" (ne hate rêzkirin)
* [[peryod]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[petrokîmya]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[peyîz]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[pifdank]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[pismam]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[pol]]: astengî di rêzkirinê de ji ber binbeşa "Binêre herwiha" (ne hate rêzkirin)
* [[qap]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[qefa]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[qolanî]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[raber]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[raketinxane]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[rapelîn]]: astengî di rêzkirinê de ji ber binbeşa "Têkildar" (ne hate rêzkirin)
* [[razang]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[razanxane]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[revandin]]: astengî di rêzkirinê de ji ber binbeşa "Formên alternatîv" (ne hate rêzkirin)
* [[rezber]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[rûniştinxane]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[serborî]]: astengî di rêzkirinê de ji ber binbeşa "Werger" (ne hate rêzkirin)
* [[surprîz]]: astengî di rêzkirinê de ji ber binbeşa "Formên alternatîv" (ne hate rêzkirin)
* [[sûperlatîv]]: astengî di rêzkirinê de ji ber binbeşa "Têkildar" (ne hate rêzkirin)
* [[tabloîd]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[texe]]: astengî di rêzkirinê de ji ber binbeşa "Formên alternatîv" (ne hate rêzkirin)
* [[teyr]]: astengî di rêzkirinê de ji ber binbeşa "Hevmane" (ne hate rêzkirin)
* [[tirp]]: astengî di rêzkirinê de ji ber binbeşa "Werger" (ne hate rêzkirin)
* [[tîre]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[werkirin]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[weşanxane]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[wuşe]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[xwarin]]: astengî di rêzkirinê de ji ber binbeşa "Têkildar" (ne hate rêzkirin)
* [[zang]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[zirz]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[zoolojî]]: astengî di rêzkirinê de ji ber binbeşa "Têkildar" (ne hate rêzkirin)
* [[zêrandin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[çiriya yekem]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[çîvçîv]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[şepilîn]]: astengî di rêzkirinê de ji ber binbeşa "Tewandin" (ne hate rêzkirin)
* [[şor]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[şêl]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[şêwr]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[paş guhê xwe ve neavêtin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[paşguh nekirin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[piştguh nekirin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[şehde]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[nahiye]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[zoolog]]: astengî di rêzkirinê de ji ber binbeşa "Têkildar" (ne hate rêzkirin)
* [[heywannas]]: astengî di rêzkirinê de ji ber binbeşa "Têkildar" (ne hate rêzkirin)
* [[bêdengahî]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[aya]]: astengî di rêzkirinê de ji ber binbeşa "Hevmane" (ne hate rêzkirin)
* [[bijîşk]]: astengî di rêzkirinê de ji ber binbeşa "Formên alternatîv" (ne hate rêzkirin)
* [[rolyef]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[mefreq]]: astengî di rêzkirinê de ji ber binbeşa "Formên alternatîv" (ne hate rêzkirin)
* [[mahir]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[jê hez kirin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[heta]]: astengî di rêzkirinê de ji ber binbeşa "Werger" (ne hate rêzkirin)
* [[ve]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[moz]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[fîq]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[Perû]]: astengî di rêzkirinê de ji ber binbeşa "Formên alternatîv" (ne hate rêzkirin)
* [[şeyda]]: astengî di rêzkirinê de ji ber binbeşa "Formên alternatîv" (ne hate rêzkirin)
* [[dinyayî]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[email]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[ser jê kirin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[leq]]: astengî di rêzkirinê de ji ber binbeşa "Formên alternatîv" (ne hate rêzkirin)
* [[kara]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[geben]]: astengî di rêzkirinê de ji ber binbeşa "Tewandin" (ne hate rêzkirin)
* [[benutzen]]: astengî di rêzkirinê de ji ber binbeşa "Tewandin" (ne hate rêzkirin)
* [[micewir]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[soldir]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[pirlanta]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[-siz]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[zarok ji ber birin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[ji holê rakirin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[zembeq]]: astengî di rêzkirinê de ji ber binbeşa "Herwiha binêre" (ne hate rêzkirin)
* [[Malî]]: astengî di rêzkirinê de ji ber binbeşa "Herwiha binêre" (ne hate rêzkirin)
* [[alaz]]: astengî di rêzkirinê de ji ber binbeşa "Werger" (ne hate rêzkirin)
* [[Çîn]]: astengî di rêzkirinê de ji ber binbeşa "Formên alternatîv" (ne hate rêzkirin)
* [[Tariq]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[tiris]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[zindî]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[Birhan]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[kirêgir]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[-ing]]: astengî di rêzkirinê de ji ber binbeşa "Formên alternatîv" (ne hate rêzkirin)
* [[abnehmen]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[hezîran]]: astengî di rêzkirinê de ji ber binbeşa "Herwiha binêre" (ne hate rêzkirin)
* [[sabir]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[lûf]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[Zêw]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[xeritandî]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[doqişk]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[eletewş]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[doxînsist]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[pol]]: astengî di rêzkirinê de ji ber binbeşa "Herwiha binêre" (ne hate rêzkirin)
* [[jixwe]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[pep]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[tacikî]]: astengî di rêzkirinê de ji ber binbeşa "Werger" (ne hate rêzkirin)
* [[unîk]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[dest kirin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[vidi]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[pêk kirin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[şûştinxane]]: astengî di rêzkirinê de ji ber binbeşa "Bide ber" (ne hate rêzkirin)
* [[sazvan]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[lê sor kirin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[li ba kirin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[binpê kirin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[lenger]]: astengî di rêzkirinê de ji ber binbeşa "Werger" (ne hate rêzkirin)
* [[Regina]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[Adie]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[rê lê xweş kirin]]: astengî di rêzkirinê de ji ber binbeşa "Jê" (ne hate rêzkirin)
* [[quç]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[Tonya]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[Conan]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
* [[spinster]]: astengî di rêzkirinê de ji ber binbeşa "Herwiha binêre" (ne hate rêzkirin)
* [[دریا]]: astengî di rêzkirinê de ji ber binbeşa "Hevmane" (ne hate rêzkirin)
* [[پیک]]: astengî di rêzkirinê de ji ber binbeşa "Etîmolojî" (ne hate rêzkirin)
== Binbeşên dubare ên hatin rakirin ==
* [[abschaffen]]: du binbeşên "Etîmolojî" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
* [[Ermenistan]]: du binbeşên "Formên alternatîv" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
* [[siwarbûn]]: du binbeşên "Etîmolojî" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
* [[asêbûn]]: du binbeşên "Etîmolojî" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
* [[qelewbûn]]: du binbeşên "Etîmolojî" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
* [[belavbûn]]: du binbeşên "Etîmolojî" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
* [[lawazbûn]]: du binbeşên "Etîmolojî" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
* [[kunbûn]]: du binbeşên "Etîmolojî" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
* [[herambûn]]: du binbeşên "Etîmolojî" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
* [[rêzbûn]]: du binbeşên "Etîmolojî" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
* [[qelsbûn]]: du binbeşên "Etîmolojî" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
* [[bêzarbûn]]: du binbeşên "Etîmolojî" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
* [[xerîbbûn]]: du binbeşên "Etîmolojî" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
* [[dirêjbûn]]: du binbeşên "Etîmolojî" bi heman naverokê hebûn, yek jê hat rakirin, ji kerema xwe kontrol bike
dpjvponwggo9tc7h02048bjl4oy1t9v
Bikarhêner:Wikihez/Gadget-test/loader.js
2
1358398
6534963
6534839
2026-09-20T18:41:40Z
Wikihez
7344
DefSideBoxes: strike a quotation queued for removal; a failed hand-over no longer leaves the form disabled
6534963
javascript
text/javascript
// <nowiki>
// Test harness for the gadgets. Loaded from [[MediaWiki:Gadget-test.js]], which is one line:
// mw.loader.load('//ku.wiktionary.org/w/index.php?title=Bikarhêner:Wikihez/Gadget-test/loader.js&action=raw&ctype=text/javascript');
//
// Runs the userspace copies in place of the site's gadgets, under the same module names. A gadget the
// wiki has registered but not yet loaded is implemented here from the test page instead, so its
// dependencies are the real ones from [[MediaWiki:Gadgets-definition]]. One already loading from the wiki
// cannot be replaced: turn the default gadgets that load it (start and wikt.dsb load wikt.core) off in
// Special:Preferences while testing, and this names any that are still on.
//
// MODULES lists only the gadgets with changes not yet live; everything else runs the site's copy. A
// gadget is added back when it is being changed, and taken out once that change is deployed.
(function () {
"use strict";
// Set synchronously, before add-examples' DOM-ready pass, so its own inline link is never built.
window.wikt = window.wikt || {};
wikt.addExamples = wikt.addExamples || {};
wikt.addExamples.suppressLinks = true;
// Subpage segments are case-sensitive, so BASE must match the pages exactly.
var BASE = "Bikarhêner:Wikihez/Gadget-test";
// A value carrying a namespace is used as the whole title; anything else is appended to BASE.
function pageTitle(suffix) {
return suffix.indexOf(":") !== -1 ? suffix : BASE + suffix;
}
// module name -> package ({main, files}) or plain script ({script}), plus styles.
// deferred held back until something asks for it, as production fetches it only then
// start asked for on page load, as production loads a default gadget
// deps only for a gadget not yet in Gadgets-definition; a registered one keeps the wiki's
// actions with start, only on these wgAction values, as a definition's "actions=" does
var MODULES = {
"ext.gadget.wikt.edit": {
main: "wikt.edit.js",
files: {
"wikt.edit.js": "/wikt-edit.js",
"wikt.edit/util.js": "/util.js",
"wikt.edit/wikitext.js": "/wikitext.js",
"wikt.edit/langs.js": "/langs.js",
"wikt.edit/labels.js": "/labels.js",
"wikt.edit/posparams.js": "/posparams.js",
"wikt.edit/data-main.json": "/data-main.json",
"wikt.edit/ziman-core.json": "/ziman-core.json"
},
styles: ["/wikt-edit.css"]
},
// Turn the site's "wikt.dsb" off in Preferences while this is armed, or the wiki's copy wins.
"ext.gadget.wikt.dsb": {
start: true,
main: "wikt.dsb.js",
files: { "wikt.dsb.js": "/dsb.js" },
styles: ["/dsb.css"]
},
// The launcher (the site's "start" gadget) asks for the wizard on the first click.
"ext.gadget.wikt.start-wizard": {
deferred: true,
main: "wikt.start-wizard.js",
files: { "wikt.start-wizard.js": ".js" },
styles: [".css"]
},
"ext.gadget.wikt.cite": {
main: "wikt.cite.js",
files: {
"wikt.cite.js": "/cite.js",
"wikt.cite/etimon.js": "/etimon.js"
},
styles: ["/cite.css"]
}
};
// Force dataType "text": otherwise jQuery sees action=raw's JavaScript Content-Type and runs it.
function raw(suffix) {
return $.get({
url: mw.util.getUrl(pageTitle(suffix), { action: "raw" }),
dataType: "text"
});
}
function run() {
var names = Object.keys(MODULES);
// A module the wiki has started loading cannot be implemented a second time.
var busy = names.filter(function (name) {
var state = mw.loader.getState(name);
return state !== null && state !== "registered";
});
if (busy.length) {
console.warn("loader.js: already loaded from the wiki, so the site's copy runs: " + busy.join(", "));
mw.notify("loader.js: van amûran di Tercîhan de bigire, da ku kopiya ceribandinê bixebite: " +
busy.map(function (n) { return n.replace("ext.gadget.", ""); }).join(", "), { type: "warn" });
}
var ours = names.filter(function (name) {
return busy.indexOf(name) === -1;
});
// Every distinct page this harness needs, fetched once each.
var suffixes = [];
function need(suffix) {
if (suffixes.indexOf(suffix) === -1) {
suffixes.push(suffix);
}
}
ours.forEach(function (name) {
var m = MODULES[name];
if (m.script) {
need(m.script);
} else {
Object.keys(m.files).forEach(function (key) { need(m.files[key]); });
}
m.styles.forEach(need);
});
if (!suffixes.length) {
return;
}
$.when.apply($, suffixes.map(raw)).done(function () {
var args = suffixes.length === 1 ? [arguments] : [].slice.call(arguments);
var text = {};
suffixes.forEach(function (s, i) {
text[s] = args[i][0];
});
// Styles go in directly: a stylesheet passed to implement() that never fires its load event
// leaves the module stuck in "executing", and everything queued behind it with it.
function addStyles(styles) {
(styles || []).forEach(function (suffix) {
if (text[suffix]) {
mw.util.addCSS(text[suffix]);
}
});
}
var bad = null;
var payloads = {};
ours.forEach(function (name) {
var m = MODULES[name];
if (m.script) {
payloads[name] = new Function("$", "jQuery", "require", "module", text[m.script]);
return;
}
var files = {};
Object.keys(m.files).forEach(function (key) {
var src = text[m.files[key]];
if (key.slice(-5) === ".json") {
try {
files[key] = JSON.parse(src);
} catch (e) {
bad = m.files[key] + " nederbasdar e (JSON parse ket): " + e.message;
}
return;
}
files[key] = new Function("require", "module", "exports", src);
});
payloads[name] = { main: m.main, files: files };
});
if (bad) {
mw.notify(bad, { type: "error" });
return;
}
// implement() runs a module as soon as its dependencies are ready, so a deferred one is
// handed over only when something asks for it.
function implement(name) {
if (!payloads[name]) {
return;
}
var payload = payloads[name];
delete payloads[name];
if (mw.loader.getState(name) === null) {
mw.loader.register(name, "0", MODULES[name].deps || []);
} else if (mw.loader.getState(name) !== "registered") {
console.warn("loader.js: " + name + " is already loading from the wiki; its copy runs.");
return;
}
mw.loader.implement(name, payload);
addStyles(MODULES[name].styles);
}
// A deferred module reached as another's dependency is handed over too, or ResourceLoader
// fetches the site's copy of it instead.
function withDeps(list) {
var out = [];
(function walk(items) {
(items || []).forEach(function (n) {
if (out.indexOf(n) !== -1) {
return;
}
out.push(n);
var entry = mw.loader.moduleRegistry[n];
walk(entry ? entry.dependencies : MODULES[n] && MODULES[n].deps);
});
}(list));
return out;
}
var origUsing = mw.loader.using;
mw.loader.using = function (modules) {
withDeps([].concat(modules || [])).forEach(implement);
return origUsing.apply(this, arguments);
};
ours.forEach(function (name) {
if (!MODULES[name].deferred) {
implement(name);
}
});
var action = mw.config.get("wgAction");
var starting = ours.filter(function (name) {
var m = MODULES[name];
return m.start && (!m.actions || m.actions.indexOf(action) !== -1);
});
var settled = false;
mw.loader.using(starting).then(function () {
settled = true;
console.log("loader.js: started " + starting.join(", "));
}, function (err) {
settled = true;
mw.notify("Xeta barkirinê: " + err, { type: "error" });
});
// A dependency stuck half-loaded leaves using() pending forever, with no error at all.
setTimeout(function () {
if (settled) {
return;
}
var stuck = withDeps(starting)
.filter(function (m) { return mw.loader.getState(m) !== "ready"; })
.map(function (m) { return m + "=" + mw.loader.getState(m); });
mw.notify("Barkirin asê ma: " + stuck.join(", "), { type: "warn" });
}, 10000);
}).fail(function () {
mw.notify("Nikare hemû rûpelên " + BASE + " bixwîne: " + suffixes.join(", "), { type: "error" });
});
}
// The test gadget declares no dependencies, so mw.util is not there yet when this runs.
mw.loader.using("mediawiki.util").then(run);
})();
// </nowiki>
d25f4j4j55qkl4877odu7jje6antbdb
6534966
6534963
2026-09-20T18:48:02Z
Wikihez
7344
Test loader: run the local add-examples too, so the queue sees its removal flag
6534966
javascript
text/javascript
// <nowiki>
// Test harness for the gadgets. Loaded from [[MediaWiki:Gadget-test.js]], which is one line:
// mw.loader.load('//ku.wiktionary.org/w/index.php?title=Bikarhêner:Wikihez/Gadget-test/loader.js&action=raw&ctype=text/javascript');
//
// Runs the userspace copies in place of the site's gadgets, under the same module names. A gadget the
// wiki has registered but not yet loaded is implemented here from the test page instead, so its
// dependencies are the real ones from [[MediaWiki:Gadgets-definition]]. One already loading from the wiki
// cannot be replaced: turn the default gadgets that load it (start and wikt.dsb load wikt.core) off in
// Special:Preferences while testing, and this names any that are still on.
//
// MODULES lists only the gadgets with changes not yet live; everything else runs the site's copy. A
// gadget is added back when it is being changed, and taken out once that change is deployed.
(function () {
"use strict";
// Set synchronously, before add-examples' DOM-ready pass, so its own inline link is never built.
window.wikt = window.wikt || {};
wikt.addExamples = wikt.addExamples || {};
wikt.addExamples.suppressLinks = true;
// Subpage segments are case-sensitive, so BASE must match the pages exactly.
var BASE = "Bikarhêner:Wikihez/Gadget-test";
// A value carrying a namespace is used as the whole title; anything else is appended to BASE.
function pageTitle(suffix) {
return suffix.indexOf(":") !== -1 ? suffix : BASE + suffix;
}
// module name -> package ({main, files}) or plain script ({script}), plus styles.
// deferred held back until something asks for it, as production fetches it only then
// start asked for on page load, as production loads a default gadget
// deps only for a gadget not yet in Gadgets-definition; a registered one keeps the wiki's
// actions with start, only on these wgAction values, as a definition's "actions=" does
var MODULES = {
"ext.gadget.wikt.edit": {
main: "wikt.edit.js",
files: {
"wikt.edit.js": "/wikt-edit.js",
"wikt.edit/util.js": "/util.js",
"wikt.edit/wikitext.js": "/wikitext.js",
"wikt.edit/langs.js": "/langs.js",
"wikt.edit/labels.js": "/labels.js",
"wikt.edit/posparams.js": "/posparams.js",
"wikt.edit/data-main.json": "/data-main.json",
"wikt.edit/ziman-core.json": "/ziman-core.json"
},
styles: ["/wikt-edit.css"]
},
// Its own page, not a Gadget-test subpage: a value carrying a namespace is used as the title.
"ext.gadget.wikt.add-examples": {
deferred: true,
main: "wikt.add-examples.js",
files: { "wikt.add-examples.js": "Bikarhêner:Wikihez/Gadget-wikt.add-examples.js" },
styles: ["Bikarhêner:Wikihez/Gadget-wikt.add-examples.css"]
},
// Turn the site's "wikt.dsb" off in Preferences while this is armed, or the wiki's copy wins.
"ext.gadget.wikt.dsb": {
start: true,
main: "wikt.dsb.js",
files: { "wikt.dsb.js": "/dsb.js" },
styles: ["/dsb.css"]
},
// The launcher (the site's "start" gadget) asks for the wizard on the first click.
"ext.gadget.wikt.start-wizard": {
deferred: true,
main: "wikt.start-wizard.js",
files: { "wikt.start-wizard.js": ".js" },
styles: [".css"]
},
"ext.gadget.wikt.cite": {
main: "wikt.cite.js",
files: {
"wikt.cite.js": "/cite.js",
"wikt.cite/etimon.js": "/etimon.js"
},
styles: ["/cite.css"]
}
};
// Force dataType "text": otherwise jQuery sees action=raw's JavaScript Content-Type and runs it.
function raw(suffix) {
return $.get({
url: mw.util.getUrl(pageTitle(suffix), { action: "raw" }),
dataType: "text"
});
}
function run() {
var names = Object.keys(MODULES);
// A module the wiki has started loading cannot be implemented a second time.
var busy = names.filter(function (name) {
var state = mw.loader.getState(name);
return state !== null && state !== "registered";
});
if (busy.length) {
console.warn("loader.js: already loaded from the wiki, so the site's copy runs: " + busy.join(", "));
mw.notify("loader.js: van amûran di Tercîhan de bigire, da ku kopiya ceribandinê bixebite: " +
busy.map(function (n) { return n.replace("ext.gadget.", ""); }).join(", "), { type: "warn" });
}
var ours = names.filter(function (name) {
return busy.indexOf(name) === -1;
});
// Every distinct page this harness needs, fetched once each.
var suffixes = [];
function need(suffix) {
if (suffixes.indexOf(suffix) === -1) {
suffixes.push(suffix);
}
}
ours.forEach(function (name) {
var m = MODULES[name];
if (m.script) {
need(m.script);
} else {
Object.keys(m.files).forEach(function (key) { need(m.files[key]); });
}
m.styles.forEach(need);
});
if (!suffixes.length) {
return;
}
$.when.apply($, suffixes.map(raw)).done(function () {
var args = suffixes.length === 1 ? [arguments] : [].slice.call(arguments);
var text = {};
suffixes.forEach(function (s, i) {
text[s] = args[i][0];
});
// Styles go in directly: a stylesheet passed to implement() that never fires its load event
// leaves the module stuck in "executing", and everything queued behind it with it.
function addStyles(styles) {
(styles || []).forEach(function (suffix) {
if (text[suffix]) {
mw.util.addCSS(text[suffix]);
}
});
}
var bad = null;
var payloads = {};
ours.forEach(function (name) {
var m = MODULES[name];
if (m.script) {
payloads[name] = new Function("$", "jQuery", "require", "module", text[m.script]);
return;
}
var files = {};
Object.keys(m.files).forEach(function (key) {
var src = text[m.files[key]];
if (key.slice(-5) === ".json") {
try {
files[key] = JSON.parse(src);
} catch (e) {
bad = m.files[key] + " nederbasdar e (JSON parse ket): " + e.message;
}
return;
}
files[key] = new Function("require", "module", "exports", src);
});
payloads[name] = { main: m.main, files: files };
});
if (bad) {
mw.notify(bad, { type: "error" });
return;
}
// implement() runs a module as soon as its dependencies are ready, so a deferred one is
// handed over only when something asks for it.
function implement(name) {
if (!payloads[name]) {
return;
}
var payload = payloads[name];
delete payloads[name];
if (mw.loader.getState(name) === null) {
mw.loader.register(name, "0", MODULES[name].deps || []);
} else if (mw.loader.getState(name) !== "registered") {
console.warn("loader.js: " + name + " is already loading from the wiki; its copy runs.");
return;
}
mw.loader.implement(name, payload);
addStyles(MODULES[name].styles);
}
// A deferred module reached as another's dependency is handed over too, or ResourceLoader
// fetches the site's copy of it instead.
function withDeps(list) {
var out = [];
(function walk(items) {
(items || []).forEach(function (n) {
if (out.indexOf(n) !== -1) {
return;
}
out.push(n);
var entry = mw.loader.moduleRegistry[n];
walk(entry ? entry.dependencies : MODULES[n] && MODULES[n].deps);
});
}(list));
return out;
}
var origUsing = mw.loader.using;
mw.loader.using = function (modules) {
withDeps([].concat(modules || [])).forEach(implement);
return origUsing.apply(this, arguments);
};
ours.forEach(function (name) {
if (!MODULES[name].deferred) {
implement(name);
}
});
var action = mw.config.get("wgAction");
var starting = ours.filter(function (name) {
var m = MODULES[name];
return m.start && (!m.actions || m.actions.indexOf(action) !== -1);
});
var settled = false;
mw.loader.using(starting).then(function () {
settled = true;
console.log("loader.js: started " + starting.join(", "));
}, function (err) {
settled = true;
mw.notify("Xeta barkirinê: " + err, { type: "error" });
});
// A dependency stuck half-loaded leaves using() pending forever, with no error at all.
setTimeout(function () {
if (settled) {
return;
}
var stuck = withDeps(starting)
.filter(function (m) { return mw.loader.getState(m) !== "ready"; })
.map(function (m) { return m + "=" + mw.loader.getState(m); });
mw.notify("Barkirin asê ma: " + stuck.join(", "), { type: "warn" });
}, 10000);
}).fail(function () {
mw.notify("Nikare hemû rûpelên " + BASE + " bixwîne: " + suffixes.join(", "), { type: "error" });
});
}
// The test gadget declares no dependencies, so mw.util is not there yet when this runs.
mw.loader.using("mediawiki.util").then(run);
})();
// </nowiki>
6bhcu09t7qi9g3r67mmbeqjnc79z72n
6534993
6534966
2026-09-20T19:39:14Z
Wikihez
7344
Test loader: run the local dsb-panels too
6534993
javascript
text/javascript
// <nowiki>
// Test harness for the gadgets. Loaded from [[MediaWiki:Gadget-test.js]], which is one line:
// mw.loader.load('//ku.wiktionary.org/w/index.php?title=Bikarhêner:Wikihez/Gadget-test/loader.js&action=raw&ctype=text/javascript');
//
// Runs the userspace copies in place of the site's gadgets, under the same module names. A gadget the
// wiki has registered but not yet loaded is implemented here from the test page instead, so its
// dependencies are the real ones from [[MediaWiki:Gadgets-definition]]. One already loading from the wiki
// cannot be replaced: turn the default gadgets that load it (start and wikt.dsb load wikt.core) off in
// Special:Preferences while testing, and this names any that are still on.
//
// MODULES lists only the gadgets with changes not yet live; everything else runs the site's copy. A
// gadget is added back when it is being changed, and taken out once that change is deployed.
(function () {
"use strict";
// Set synchronously, before add-examples' DOM-ready pass, so its own inline link is never built.
window.wikt = window.wikt || {};
wikt.addExamples = wikt.addExamples || {};
wikt.addExamples.suppressLinks = true;
// Subpage segments are case-sensitive, so BASE must match the pages exactly.
var BASE = "Bikarhêner:Wikihez/Gadget-test";
// A value carrying a namespace is used as the whole title; anything else is appended to BASE.
function pageTitle(suffix) {
return suffix.indexOf(":") !== -1 ? suffix : BASE + suffix;
}
// module name -> package ({main, files}) or plain script ({script}), plus styles.
// deferred held back until something asks for it, as production fetches it only then
// start asked for on page load, as production loads a default gadget
// deps only for a gadget not yet in Gadgets-definition; a registered one keeps the wiki's
// actions with start, only on these wgAction values, as a definition's "actions=" does
var MODULES = {
"ext.gadget.wikt.edit": {
main: "wikt.edit.js",
files: {
"wikt.edit.js": "/wikt-edit.js",
"wikt.edit/util.js": "/util.js",
"wikt.edit/wikitext.js": "/wikitext.js",
"wikt.edit/langs.js": "/langs.js",
"wikt.edit/labels.js": "/labels.js",
"wikt.edit/posparams.js": "/posparams.js",
"wikt.edit/data-main.json": "/data-main.json",
"wikt.edit/ziman-core.json": "/ziman-core.json"
},
styles: ["/wikt-edit.css"]
},
// Its own page, not a Gadget-test subpage: a value carrying a namespace is used as the title.
"ext.gadget.wikt.add-examples": {
deferred: true,
main: "wikt.add-examples.js",
files: { "wikt.add-examples.js": "Bikarhêner:Wikihez/Gadget-wikt.add-examples.js" },
styles: ["Bikarhêner:Wikihez/Gadget-wikt.add-examples.css"]
},
// Turn the site's "wikt.dsb" off in Preferences while this is armed, or the wiki's copy wins.
"ext.gadget.wikt.dsb": {
start: true,
main: "wikt.dsb.js",
files: { "wikt.dsb.js": "/dsb.js" },
styles: ["/dsb.css"]
},
// The launcher (the site's "start" gadget) asks for the wizard on the first click.
"ext.gadget.wikt.start-wizard": {
deferred: true,
main: "wikt.start-wizard.js",
files: { "wikt.start-wizard.js": ".js" },
styles: [".css"]
},
// Where the sense panels live: dsb.js asks for it on the first pencil click.
"ext.gadget.wikt.dsb-panels": {
deferred: true,
main: "wikt.dsb-panels.js",
files: { "wikt.dsb-panels.js": "/dsb-panels.js" },
styles: ["/dsb-panels.css"]
},
"ext.gadget.wikt.cite": {
main: "wikt.cite.js",
files: {
"wikt.cite.js": "/cite.js",
"wikt.cite/etimon.js": "/etimon.js"
},
styles: ["/cite.css"]
}
};
// Force dataType "text": otherwise jQuery sees action=raw's JavaScript Content-Type and runs it.
function raw(suffix) {
return $.get({
url: mw.util.getUrl(pageTitle(suffix), { action: "raw" }),
dataType: "text"
});
}
function run() {
var names = Object.keys(MODULES);
// A module the wiki has started loading cannot be implemented a second time.
var busy = names.filter(function (name) {
var state = mw.loader.getState(name);
return state !== null && state !== "registered";
});
if (busy.length) {
console.warn("loader.js: already loaded from the wiki, so the site's copy runs: " + busy.join(", "));
mw.notify("loader.js: van amûran di Tercîhan de bigire, da ku kopiya ceribandinê bixebite: " +
busy.map(function (n) { return n.replace("ext.gadget.", ""); }).join(", "), { type: "warn" });
}
var ours = names.filter(function (name) {
return busy.indexOf(name) === -1;
});
// Every distinct page this harness needs, fetched once each.
var suffixes = [];
function need(suffix) {
if (suffixes.indexOf(suffix) === -1) {
suffixes.push(suffix);
}
}
ours.forEach(function (name) {
var m = MODULES[name];
if (m.script) {
need(m.script);
} else {
Object.keys(m.files).forEach(function (key) { need(m.files[key]); });
}
m.styles.forEach(need);
});
if (!suffixes.length) {
return;
}
$.when.apply($, suffixes.map(raw)).done(function () {
var args = suffixes.length === 1 ? [arguments] : [].slice.call(arguments);
var text = {};
suffixes.forEach(function (s, i) {
text[s] = args[i][0];
});
// Styles go in directly: a stylesheet passed to implement() that never fires its load event
// leaves the module stuck in "executing", and everything queued behind it with it.
function addStyles(styles) {
(styles || []).forEach(function (suffix) {
if (text[suffix]) {
mw.util.addCSS(text[suffix]);
}
});
}
var bad = null;
var payloads = {};
ours.forEach(function (name) {
var m = MODULES[name];
if (m.script) {
payloads[name] = new Function("$", "jQuery", "require", "module", text[m.script]);
return;
}
var files = {};
Object.keys(m.files).forEach(function (key) {
var src = text[m.files[key]];
if (key.slice(-5) === ".json") {
try {
files[key] = JSON.parse(src);
} catch (e) {
bad = m.files[key] + " nederbasdar e (JSON parse ket): " + e.message;
}
return;
}
files[key] = new Function("require", "module", "exports", src);
});
payloads[name] = { main: m.main, files: files };
});
if (bad) {
mw.notify(bad, { type: "error" });
return;
}
// implement() runs a module as soon as its dependencies are ready, so a deferred one is
// handed over only when something asks for it.
function implement(name) {
if (!payloads[name]) {
return;
}
var payload = payloads[name];
delete payloads[name];
if (mw.loader.getState(name) === null) {
mw.loader.register(name, "0", MODULES[name].deps || []);
} else if (mw.loader.getState(name) !== "registered") {
console.warn("loader.js: " + name + " is already loading from the wiki; its copy runs.");
return;
}
mw.loader.implement(name, payload);
addStyles(MODULES[name].styles);
}
// A deferred module reached as another's dependency is handed over too, or ResourceLoader
// fetches the site's copy of it instead.
function withDeps(list) {
var out = [];
(function walk(items) {
(items || []).forEach(function (n) {
if (out.indexOf(n) !== -1) {
return;
}
out.push(n);
var entry = mw.loader.moduleRegistry[n];
walk(entry ? entry.dependencies : MODULES[n] && MODULES[n].deps);
});
}(list));
return out;
}
var origUsing = mw.loader.using;
mw.loader.using = function (modules) {
withDeps([].concat(modules || [])).forEach(implement);
return origUsing.apply(this, arguments);
};
ours.forEach(function (name) {
if (!MODULES[name].deferred) {
implement(name);
}
});
var action = mw.config.get("wgAction");
var starting = ours.filter(function (name) {
var m = MODULES[name];
return m.start && (!m.actions || m.actions.indexOf(action) !== -1);
});
var settled = false;
mw.loader.using(starting).then(function () {
settled = true;
console.log("loader.js: started " + starting.join(", "));
}, function (err) {
settled = true;
mw.notify("Xeta barkirinê: " + err, { type: "error" });
});
// A dependency stuck half-loaded leaves using() pending forever, with no error at all.
setTimeout(function () {
if (settled) {
return;
}
var stuck = withDeps(starting)
.filter(function (m) { return mw.loader.getState(m) !== "ready"; })
.map(function (m) { return m + "=" + mw.loader.getState(m); });
mw.notify("Barkirin asê ma: " + stuck.join(", "), { type: "warn" });
}, 10000);
}).fail(function () {
mw.notify("Nikare hemû rûpelên " + BASE + " bixwîne: " + suffixes.join(", "), { type: "error" });
});
}
// The test gadget declares no dependencies, so mw.util is not there yet when this runs.
mw.loader.using("mediawiki.util").then(run);
})();
// </nowiki>
8fqf9tm3scg31v56zmleouq2iwagzzm
6535005
6534993
2026-09-20T20:04:07Z
Wikihez
7344
Test loader: fetch the test pages uncached
6535005
javascript
text/javascript
// <nowiki>
// Test harness for the gadgets. Loaded from [[MediaWiki:Gadget-test.js]], which is one line:
// mw.loader.load('//ku.wiktionary.org/w/index.php?title=Bikarhêner:Wikihez/Gadget-test/loader.js&action=raw&ctype=text/javascript');
//
// Runs the userspace copies in place of the site's gadgets, under the same module names. A gadget the
// wiki has registered but not yet loaded is implemented here from the test page instead, so its
// dependencies are the real ones from [[MediaWiki:Gadgets-definition]]. One already loading from the wiki
// cannot be replaced: turn the default gadgets that load it (start and wikt.dsb load wikt.core) off in
// Special:Preferences while testing, and this names any that are still on.
//
// MODULES lists only the gadgets with changes not yet live; everything else runs the site's copy. A
// gadget is added back when it is being changed, and taken out once that change is deployed.
(function () {
"use strict";
// Set synchronously, before add-examples' DOM-ready pass, so its own inline link is never built.
window.wikt = window.wikt || {};
wikt.addExamples = wikt.addExamples || {};
wikt.addExamples.suppressLinks = true;
// Subpage segments are case-sensitive, so BASE must match the pages exactly.
var BASE = "Bikarhêner:Wikihez/Gadget-test";
// A value carrying a namespace is used as the whole title; anything else is appended to BASE.
function pageTitle(suffix) {
return suffix.indexOf(":") !== -1 ? suffix : BASE + suffix;
}
// module name -> package ({main, files}) or plain script ({script}), plus styles.
// deferred held back until something asks for it, as production fetches it only then
// start asked for on page load, as production loads a default gadget
// deps only for a gadget not yet in Gadgets-definition; a registered one keeps the wiki's
// actions with start, only on these wgAction values, as a definition's "actions=" does
var MODULES = {
"ext.gadget.wikt.edit": {
main: "wikt.edit.js",
files: {
"wikt.edit.js": "/wikt-edit.js",
"wikt.edit/util.js": "/util.js",
"wikt.edit/wikitext.js": "/wikitext.js",
"wikt.edit/langs.js": "/langs.js",
"wikt.edit/labels.js": "/labels.js",
"wikt.edit/posparams.js": "/posparams.js",
"wikt.edit/data-main.json": "/data-main.json",
"wikt.edit/ziman-core.json": "/ziman-core.json"
},
styles: ["/wikt-edit.css"]
},
// Its own page, not a Gadget-test subpage: a value carrying a namespace is used as the title.
"ext.gadget.wikt.add-examples": {
deferred: true,
main: "wikt.add-examples.js",
files: { "wikt.add-examples.js": "Bikarhêner:Wikihez/Gadget-wikt.add-examples.js" },
styles: ["Bikarhêner:Wikihez/Gadget-wikt.add-examples.css"]
},
// Turn the site's "wikt.dsb" off in Preferences while this is armed, or the wiki's copy wins.
"ext.gadget.wikt.dsb": {
start: true,
main: "wikt.dsb.js",
files: { "wikt.dsb.js": "/dsb.js" },
styles: ["/dsb.css"]
},
// The launcher (the site's "start" gadget) asks for the wizard on the first click.
"ext.gadget.wikt.start-wizard": {
deferred: true,
main: "wikt.start-wizard.js",
files: { "wikt.start-wizard.js": ".js" },
styles: [".css"]
},
// Where the sense panels live: dsb.js asks for it on the first pencil click.
"ext.gadget.wikt.dsb-panels": {
deferred: true,
main: "wikt.dsb-panels.js",
files: { "wikt.dsb-panels.js": "/dsb-panels.js" },
styles: ["/dsb-panels.css"]
},
"ext.gadget.wikt.cite": {
main: "wikt.cite.js",
files: {
"wikt.cite.js": "/cite.js",
"wikt.cite/etimon.js": "/etimon.js"
},
styles: ["/cite.css"]
}
};
// Force dataType "text": otherwise jQuery sees action=raw's JavaScript Content-Type and runs it.
// maxage/smaxage=0 and cache:false because the browser otherwise serves a cached copy of the page,
// and a test run against yesterday's file looks like a bug in today's.
function raw(suffix) {
return $.get({
url: mw.util.getUrl(pageTitle(suffix), { action: "raw", maxage: 0, smaxage: 0 }),
dataType: "text",
cache: false
});
}
function run() {
var names = Object.keys(MODULES);
// A module the wiki has started loading cannot be implemented a second time.
var busy = names.filter(function (name) {
var state = mw.loader.getState(name);
return state !== null && state !== "registered";
});
if (busy.length) {
console.warn("loader.js: already loaded from the wiki, so the site's copy runs: " + busy.join(", "));
mw.notify("loader.js: van amûran di Tercîhan de bigire, da ku kopiya ceribandinê bixebite: " +
busy.map(function (n) { return n.replace("ext.gadget.", ""); }).join(", "), { type: "warn" });
}
var ours = names.filter(function (name) {
return busy.indexOf(name) === -1;
});
// Every distinct page this harness needs, fetched once each.
var suffixes = [];
function need(suffix) {
if (suffixes.indexOf(suffix) === -1) {
suffixes.push(suffix);
}
}
ours.forEach(function (name) {
var m = MODULES[name];
if (m.script) {
need(m.script);
} else {
Object.keys(m.files).forEach(function (key) { need(m.files[key]); });
}
m.styles.forEach(need);
});
if (!suffixes.length) {
return;
}
$.when.apply($, suffixes.map(raw)).done(function () {
var args = suffixes.length === 1 ? [arguments] : [].slice.call(arguments);
var text = {};
suffixes.forEach(function (s, i) {
text[s] = args[i][0];
});
// Styles go in directly: a stylesheet passed to implement() that never fires its load event
// leaves the module stuck in "executing", and everything queued behind it with it.
function addStyles(styles) {
(styles || []).forEach(function (suffix) {
if (text[suffix]) {
mw.util.addCSS(text[suffix]);
}
});
}
var bad = null;
var payloads = {};
ours.forEach(function (name) {
var m = MODULES[name];
if (m.script) {
payloads[name] = new Function("$", "jQuery", "require", "module", text[m.script]);
return;
}
var files = {};
Object.keys(m.files).forEach(function (key) {
var src = text[m.files[key]];
if (key.slice(-5) === ".json") {
try {
files[key] = JSON.parse(src);
} catch (e) {
bad = m.files[key] + " nederbasdar e (JSON parse ket): " + e.message;
}
return;
}
files[key] = new Function("require", "module", "exports", src);
});
payloads[name] = { main: m.main, files: files };
});
if (bad) {
mw.notify(bad, { type: "error" });
return;
}
// implement() runs a module as soon as its dependencies are ready, so a deferred one is
// handed over only when something asks for it.
function implement(name) {
if (!payloads[name]) {
return;
}
var payload = payloads[name];
delete payloads[name];
if (mw.loader.getState(name) === null) {
mw.loader.register(name, "0", MODULES[name].deps || []);
} else if (mw.loader.getState(name) !== "registered") {
console.warn("loader.js: " + name + " is already loading from the wiki; its copy runs.");
return;
}
mw.loader.implement(name, payload);
addStyles(MODULES[name].styles);
}
// A deferred module reached as another's dependency is handed over too, or ResourceLoader
// fetches the site's copy of it instead.
function withDeps(list) {
var out = [];
(function walk(items) {
(items || []).forEach(function (n) {
if (out.indexOf(n) !== -1) {
return;
}
out.push(n);
var entry = mw.loader.moduleRegistry[n];
walk(entry ? entry.dependencies : MODULES[n] && MODULES[n].deps);
});
}(list));
return out;
}
var origUsing = mw.loader.using;
mw.loader.using = function (modules) {
withDeps([].concat(modules || [])).forEach(implement);
return origUsing.apply(this, arguments);
};
ours.forEach(function (name) {
if (!MODULES[name].deferred) {
implement(name);
}
});
var action = mw.config.get("wgAction");
var starting = ours.filter(function (name) {
var m = MODULES[name];
return m.start && (!m.actions || m.actions.indexOf(action) !== -1);
});
var settled = false;
mw.loader.using(starting).then(function () {
settled = true;
console.log("loader.js: started " + starting.join(", "));
}, function (err) {
settled = true;
mw.notify("Xeta barkirinê: " + err, { type: "error" });
});
// A dependency stuck half-loaded leaves using() pending forever, with no error at all.
setTimeout(function () {
if (settled) {
return;
}
var stuck = withDeps(starting)
.filter(function (m) { return mw.loader.getState(m) !== "ready"; })
.map(function (m) { return m + "=" + mw.loader.getState(m); });
mw.notify("Barkirin asê ma: " + stuck.join(", "), { type: "warn" });
}, 10000);
}).fail(function () {
mw.notify("Nikare hemû rûpelên " + BASE + " bixwîne: " + suffixes.join(", "), { type: "error" });
});
}
// The test gadget declares no dependencies, so mw.util is not there yet when this runs.
mw.loader.using("mediawiki.util").then(run);
})();
// </nowiki>
2rfmo4wcnl67xqhu4vyp1vcru6lw51x
Şablon:J:M. Xalid Sadinî:Feqiyê Teyran: Jiyan Berhem û Helbestên wî:2013
10
1358864
6534939
6346033
2026-09-20T18:00:45Z
Wikihez
7344
6534939
wikitext
text/x-wiki
{{#invoke:quote|call_quote_template
|ku
|sal=~1620
|isbn=9789944360432
|nivîskar=w:Feqiyê Teyran;w:M. Xalid Sadînî
|edîtor=w:Süleyman Çevik; w:Selman Dilovan
|sernav=Feqiyê Teyran: Jiyan Berhem û Helbestên wî
|weşanxane=Weşanên {{w|Nûbihar (weşanxane)|Nûbihar}}
|serî=Klasîkên kurdî
|çap=7
|sala-weşandinê=2013
|cih=w:Stenbol
|textparam=jg,jêgirtin
|pageparam=r,rûpel
}}<!--
-->{{kategorî|Jêgirtinên ji Feqiyê Teyran}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
<noinclude>{{belgekirin|naverok=
{{kurterê|Şablon:J:Feqiyê Teyran:2013}}
== Bikaranîn ==
<syntaxhighlight lang="wikitext" copy>
{{J:Feqiyê Teyran:2013|jêgirtin= |rûpel= }}
</syntaxhighlight>
== TemplateData ==
<templatedata>
{
"params": {
"jêgirtin": {
"aliases": [
"jg"
]
},
"rûpel": {
"aliases": [
"r"
]
}
},
"format": "inline",
"description": "Ji bo jêgirtinên ji Feqiyê Teyran, M. Xalid Sadînî, edîtoriya Süleyman Çevik û Selman Dilovan, Feqiyê Teyran: Jiyan Berhem û Helbestên wî (Klasîkên kurdî), çapa 7an, Stenbol: Weşanên Nûbihar, di 2013 de hate weşandin"
}
</templatedata>
== Herwiha binêre ==
* {{ş|J:Feqiyê Teyran:2013}}
{{quotecat|ku}}
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Feqiyê Teyran]]
}}
</noinclude>
adaopzuz3u0i1tebmlzcblk4jxiw6qi
6534940
6534939
2026-09-20T18:01:00Z
Wikihez
7344
6534940
wikitext
text/x-wiki
{{#invoke:quote|call_quote_template
|ku
|sal=~1620
|isbn=9789944360432
|nivîskar=w:Feqiyê Teyran;w:M. Xalid Sadînî
|edîtor=w:Süleyman Çevik; w:Selman Dilovan
|sernav=Feqiyê Teyran: Jiyan Berhem û Helbestên wî
|weşanxane=Weşanên {{w|Nûbihar (weşanxane)|Nûbihar}}
|serî=Klasîkên kurdî
|çap=7
|sala-weşandinê=2013
|cih=w:Stenbol
|textparam=jg,jêgirtin
|pageparam=r,rûpel
}}<!--
-->{{kategorî|Jêgirtinên ji Feqiyê Teyran}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
<noinclude>{{belgekirin|naverok=
{{kurterê|Şablon:J:Feqiyê Teyran:2013}}
== Bikaranîn ==
<syntaxhighlight lang="wikitext" copy>
{{J:Feqiyê Teyran:2013|jêgirtin= |rûpel= }}
</syntaxhighlight>
== TemplateData ==
<templatedata>
{
"params": {
"jêgirtin": {
"aliases": [
"jg"
]
},
"rûpel": {
"aliases": [
"r"
]
}
},
"format": "inline",
"description": "Ji bo jêgirtinên ji Feqiyê Teyran, M. Xalid Sadînî, edîtoriya Süleyman Çevik û Selman Dilovan, Feqiyê Teyran: Jiyan Berhem û Helbestên wî (Klasîkên kurdî), çapa 7an, Stenbol: Weşanên Nûbihar, di 2013 de hate weşandin"
}
</templatedata>
== Herwiha binêre ==
* {{ş|J:Feqiyê Teyran:2022}}
{{quotecat|ku}}
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Feqiyê Teyran]]
}}
</noinclude>
o0ihlpz1dtnogq2eyia8ludz6ysiwjd
Bikarhêner:Wikihez/Gadget-test/dsb-panels.js
2
1358909
6534961
6534760
2026-09-20T18:41:21Z
Wikihez
7344
DefSideBoxes: strike a quotation queued for removal; a failed hand-over no longer leaves the form disabled
6534961
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
lines.splice(end, 0, marker + " " + text);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel, changes.newSense);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense) {
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
return (v && v !== originalGloss) ? v : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
function addNewSenseField() {
var $textarea = $("<textarea class='dsb-input' rows='2'></textarea>").attr("placeholder", "Maneya nû binivîse…");
getters.newSense = function () {
return $.trim($textarea.val());
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text(util.msg.anotherSense),
$textarea
);
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: "Maneyê biguhêre", build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, build: addNewSenseField, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
newSense: getters.newSense ? getters.newSense() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
$cancel.on("click", function () {
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
var undoPreview = previewSense($li, language, sectionType, sectionOrdinal,
definitionLevel);
// A new sense has no place on the page yet, so it gets a row of its own directly
// after this one -- the list numbers it, so where it will land is visible.
var $newSense = null;
if (changes.newSense) {
$newSense = $("<li class='dsb-sense-preview'></li>");
$li.after($newSense);
fillWithRendered($newSense, changes.newSense, function () {
$newSense.text(changes.newSense);
});
}
return function () {
undoPreview();
if ($newSense) {
$newSense.remove();
}
};
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
iwiz613p26p9xaynbjwi4g2mwy1r5rm
6534975
6534961
2026-09-20T18:59:54Z
Wikihez
7344
DefSideBoxes: compose a new sense with its word lists in one queued insertion
6534975
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense) {
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
return (v && v !== originalGloss) ? v : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// The new sense is composed here in full -- gloss plus its word lists -- because it goes onto the
// page as one insertion. A list added after saving is an ordinary edit to a sense that exists; one
// added now has no line number to be found by, so it rides along with the sense itself.
function addNewSenseField() {
var $textarea = $("<textarea class='dsb-input' rows='2'></textarea>").attr("placeholder", "Maneya nû binivîse…");
var lists = {};
var $lists = $("<div class='dsb-new-sense-lists'></div>");
var $picker = $("<div class='dsb-new-sense-picker'></div>");
function addList(group) {
if (lists[group.key]) {
lists[group.key].field.focus();
return;
}
var field = buildWordListField(group.label + "…");
lists[group.key] = { group: group, field: field };
$lists.append($("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text(group.label), field.$element));
$picker.children("[data-group='" + group.key + "']").hide();
if (onResize) {
onResize();
}
}
SENSE_GROUPS.forEach(function (group) {
$picker.append($("<a href='#' class='dsb-quick-link'></a>")
.attr("data-group", group.key)
.text("+ " + group.label)
.on("click", function (e) {
e.preventDefault();
addList(group);
}));
});
getters.newSense = function () {
return $.trim($textarea.val());
};
// "#: {{hevmane|ku|a|b}}" lines, in senseGroups' own order, exactly as the wizard writes them.
getters.newSenseExtras = function () {
var out = [];
SENSE_GROUPS.forEach(function (group) {
var entry = lists[group.key];
var words = entry ? entry.field.values() : [];
if (words.length) {
out.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
return out;
};
return $("<div class='dsb-adv-group dsb-new-sense'></div>").append(
$("<div class='dsb-adv-label'></div>").text(util.msg.anotherSense),
$textarea, $lists, $picker
);
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: "Maneyê biguhêre", build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, build: addNewSenseField, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
newSense: getters.newSense ? getters.newSense() : null,
newSenseExtras: getters.newSenseExtras ? getters.newSenseExtras() : [],
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
$cancel.on("click", function () {
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
var undoPreview = previewSense($li, language, sectionType, sectionOrdinal,
definitionLevel);
// A new sense has no place on the page yet, so it gets a row of its own directly
// after this one -- the list numbers it, so where it will land is visible.
var $newSense = null;
if (changes.newSense) {
$newSense = $("<li class='dsb-sense-preview'></li>");
$li.after($newSense);
// Rendered as the page will write it, word lists and all, so the row reads as
// the sense it is about to become rather than as the text typed in the box.
var block = ["# " + changes.newSense].concat(
(changes.newSenseExtras || []).map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
renderWikitext(block).then(function (html) {
if (!$newSense.closest("body").length) {
return;
}
var $item = parsedSenseItem(html, 1);
$newSense.empty().append($item.length ? $item.contents() :
$("<span></span>").text(changes.newSense));
announceContent($newSense);
}, function () {
$newSense.text(changes.newSense);
});
}
return function () {
undoPreview();
if ($newSense) {
$newSense.remove();
}
};
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
j17lj7oaljkz4sx93hebhehxscoga7q
6534981
6534975
2026-09-20T19:19:15Z
Wikihez
7344
DefSideBoxes: the new sense gets its own numbered row, its own buttons and its own quotations
6534981
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense) {
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
return (v && v !== originalGloss) ? v : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
function openNewSenseRow() {
var existing = $li.next(".dsb-new-sense-row");
if (existing.length) {
existing.find("textarea").first().trigger("focus");
return;
}
openQueueBox();
var lists = {};
var quotes = [];
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $box = $("<div class='dsb-new-sense'></div>");
var $text = $("<textarea class='dsb-input' rows='2'></textarea>")
.attr("placeholder", "Maneya nû binivîse…");
var $lists = $("<div class='dsb-new-sense-lists'></div>");
var $quotes = $("<div class='dsb-new-sense-quotes'></div>");
var $picker = $("<div class='dsb-new-sense-picker'></div>");
var $add = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
// "#:" lines in senseGroups' own order, then the quotations in the order they were added --
// the same order the wizard writes them in.
function extras() {
var out = [];
SENSE_GROUPS.forEach(function (group) {
var entry = lists[group.key];
var words = entry ? entry.values() : [];
if (words.length) {
out.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
return out.concat(quotes);
}
function block() {
return ["# " + $.trim($text.val())].concat(extras().map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
}
function addList(group) {
if (lists[group.key]) {
lists[group.key].$element.find("input").first().trigger("focus");
return;
}
var field = buildWordListField(group.label + "…");
lists[group.key] = field;
$lists.append($("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text(group.label), field.$element));
if (onResize) {
onResize();
}
}
// The form is opened on this sense, since a row that is not on the page has nothing for
// add-examples to attach to; only the line it builds is kept, never its page transform.
function addQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
quotes.push(job.line.replace(/^#+/, ""));
$quotes.append($("<div class='dsb-new-sense-quote'></div>").text(job.line));
job.done();
if (onResize) {
onResize();
}
});
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
SENSE_GROUPS.forEach(function (group) {
$picker.append($("<a href='#' class='dsb-quick-link'></a>").text("+ " + group.label)
.on("click", function (e) {
e.preventDefault();
addList(group);
}));
});
$picker.append($("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin")
.on("click", function (e) {
e.preventDefault();
addQuotation();
}));
$cancel.on("click", function () {
$row.remove();
hideQueueIfIdle();
if (onResize) {
onResize();
}
});
$add.on("click", function () {
var text = $.trim($text.val());
if (!text) {
$status.addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var lines = extras();
var wikitext = block();
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType,
sectionOrdinal, definitionLevel, text, lines), warnings: [] };
},
$add,
$status,
// Rendered as the page will write it, so the row reads as a sense rather than as the
// text that was typed into the box.
function () {
renderWikitext(wikitext).then(function (html) {
if (!$row.closest("body").length) {
return;
}
var $item = parsedSenseItem(html, 1);
$row.empty().append($item.length ? $item.contents() :
$("<span></span>").text(text));
announceContent($row);
}, function () {
$row.empty().text(text);
});
return function () {
$row.remove();
};
},
{ key: "newSense:" + definitionLevel.join("."), sensePath: definitionLevel,
senseLanguage: language }
);
});
$box.append($text, $lists, $quotes, $picker,
$("<div class='dsb-form-actions'></div>").append($add, $cancel, $status));
$row.append($box);
$li.after($row);
$text.trigger("focus");
if (onResize) {
onResize();
}
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: "Maneyê biguhêre", build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
me5atxl7has1h9io47og2cq3k7r02wz
6534996
6534981
2026-09-20T19:43:05Z
Wikihez
7344
DefSideBoxes: the new-sense row takes the same actions row a saved sense has
6534996
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense) {
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
return (v && v !== originalGloss) ? v : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
function openNewSenseRow() {
var existing = $li.next(".dsb-new-sense-row");
if (existing.length) {
existing.find("textarea").first().trigger("focus");
return;
}
openQueueBox();
var lists = {};
var quotes = [];
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $box = $("<div class='dsb-new-sense'></div>");
var $text = $("<textarea class='dsb-input' rows='2'></textarea>")
.attr("placeholder", "Maneya nû binivîse…");
var $lists = $("<div class='dsb-new-sense-lists'></div>");
var $quotes = $("<div class='dsb-new-sense-quotes'></div>");
var $picker = $("<div class='dsb-new-sense-picker'></div>");
var $add = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
// "#:" lines in senseGroups' own order, then the quotations in the order they were added --
// the same order the wizard writes them in.
function extras() {
var out = [];
SENSE_GROUPS.forEach(function (group) {
var entry = lists[group.key];
var words = entry ? entry.values() : [];
if (words.length) {
out.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
return out.concat(quotes);
}
function block() {
return ["# " + $.trim($text.val())].concat(extras().map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
}
function addList(group) {
if (lists[group.key]) {
lists[group.key].$element.find("input").first().trigger("focus");
return;
}
var field = buildWordListField(group.label + "…");
lists[group.key] = field;
$lists.append($("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text(group.label), field.$element));
if (onResize) {
onResize();
}
}
// The form is opened on this sense, since a row that is not on the page has nothing for
// add-examples to attach to; only the line it builds is kept, never its page transform.
function addQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
quotes.push(job.line.replace(/^#+/, ""));
$quotes.append($("<div class='dsb-new-sense-quote'></div>").text(job.line));
job.done();
if (onResize) {
onResize();
}
});
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// The same shape as a saved sense's row: the one action that is used every time sits in the
// open, the word lists behind "Hevmane û Bêhtir". There is no "Maneyê biguhêre" here -- the
// box above it is the gloss.
var $rowMenu = $("<div class='dsb-more-menu'></div>").hide();
var $rowArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $rowToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($rowArrow);
$rowToggle.on("click", function (e) {
e.preventDefault();
$rowMenu.slideToggle(function () {
$rowArrow.text($rowMenu.is(":visible") ? "▾" : "▸");
});
});
// senseGroups' own order, the order the "#:" lines are written in, as in the sense menu.
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).forEach(function (group) {
$rowMenu.append($("<a href='#' class='dsb-quick-link'></a>").text("+ " + group.label)
.on("click", function (e) {
e.preventDefault();
$rowMenu.slideUp(function () {
$rowArrow.text("▸");
});
addList(group);
}));
});
$picker.append($("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin")
.on("click", function (e) {
e.preventDefault();
addQuotation();
}), $rowToggle, $rowMenu);
$cancel.on("click", function () {
$row.remove();
hideQueueIfIdle();
if (onResize) {
onResize();
}
});
$add.on("click", function () {
var text = $.trim($text.val());
if (!text) {
$status.addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var lines = extras();
var wikitext = block();
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType,
sectionOrdinal, definitionLevel, text, lines), warnings: [] };
},
$add,
$status,
// Rendered as the page will write it, so the row reads as a sense rather than as the
// text that was typed into the box.
function () {
renderWikitext(wikitext).then(function (html) {
if (!$row.closest("body").length) {
return;
}
var $item = parsedSenseItem(html, 1);
$row.empty().append($item.length ? $item.contents() :
$("<span></span>").text(text));
announceContent($row);
}, function () {
$row.empty().text(text);
});
return function () {
$row.remove();
};
},
{ key: "newSense:" + definitionLevel.join("."), sensePath: definitionLevel,
senseLanguage: language }
);
});
$box.append($text, $lists, $quotes, $picker,
$("<div class='dsb-form-actions'></div>").append($add, $cancel, $status));
$row.append($box);
$li.after($row);
$text.trigger("focus");
if (onResize) {
onResize();
}
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: "Maneyê biguhêre", build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
p2h3l59k5j1zscjfafbla691j0taev8
6534999
6534996
2026-09-20T19:49:17Z
Wikihez
7344
DefSideBoxes: a queued new sense redraws itself and can be reopened
6534999
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense) {
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
return (v && v !== originalGloss) ? v : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
// `state` reopens a sense already queued: {text, lists, quotes} as the box last held them.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
openQueueBox();
state = state || { text: "", lists: {}, quotes: [] };
var lists = {};
var quotes = state.quotes.slice();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $box = $("<div class='dsb-new-sense'></div>");
var $text = $("<textarea class='dsb-input' rows='2'></textarea>")
.attr("placeholder", "Maneya nû binivîse…").val(state.text);
var $lists = $("<div class='dsb-new-sense-lists'></div>");
var $quotes = $("<div class='dsb-new-sense-quotes'></div>");
var $picker = $("<div class='dsb-new-sense-picker'></div>");
var $add = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
$quickRow.data("dsbNewSenseRow", $row);
function drawQuote(line) {
$quotes.append($("<div class='dsb-new-sense-quote'></div>").text(line));
}
quotes.forEach(function (extra) {
drawQuote("#" + extra);
});
// "#:" lines in senseGroups' own order, then the quotations in the order they were added --
// the same order the wizard writes them in.
function extras() {
var out = [];
SENSE_GROUPS.forEach(function (group) {
var entry = lists[group.key];
var words = entry ? entry.values() : [];
if (words.length) {
out.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
return out.concat(quotes);
}
function collected() {
var held = {};
Object.keys(lists).forEach(function (key) {
held[key] = lists[key].values();
});
return { text: $.trim($text.val()), lists: held, quotes: quotes.slice() };
}
function addList(group, words) {
if (lists[group.key]) {
lists[group.key].$element.find("input").first().trigger("focus");
return;
}
var field = buildWordListField(group.label + "…");
if (words && words.length) {
field.setValues(words);
}
lists[group.key] = field;
$lists.append($("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text(group.label), field.$element));
if (onResize) {
onResize();
}
}
// The form is opened on this sense, since a row that is not on the page has nothing for
// add-examples to attach to; only the line it builds is kept, never its page transform.
function addQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
quotes.push(job.line.replace(/^#+/, ""));
drawQuote(job.line);
job.done();
if (onResize) {
onResize();
}
});
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// The same shape as a saved sense's row: the one action that is used every time sits in the
// open, the word lists behind "Hevmane û Bêhtir". There is no "Maneyê biguhêre" here -- the
// box above it is the gloss.
var $rowMenu = $("<div class='dsb-more-menu'></div>").hide();
var $rowArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $rowToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($rowArrow);
$rowToggle.on("click", function (e) {
e.preventDefault();
$rowMenu.slideToggle(function () {
$rowArrow.text($rowMenu.is(":visible") ? "▾" : "▸");
});
});
// senseGroups' own order, the order the "#:" lines are written in, as in the sense menu.
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).forEach(function (group) {
$rowMenu.append($("<a href='#' class='dsb-quick-link'></a>").text("+ " + group.label)
.on("click", function (e) {
e.preventDefault();
$rowMenu.slideUp(function () {
$rowArrow.text("▸");
});
addList(group);
}));
});
$picker.append($("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin")
.on("click", function (e) {
e.preventDefault();
addQuotation();
}), $rowToggle, $rowMenu);
SENSE_GROUPS.forEach(function (group) {
var words = state.lists[group.key];
if (words && words.length) {
addList(group, words);
}
});
$cancel.on("click", function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
if (onResize) {
onResize();
}
});
$add.on("click", function () {
var text = $.trim($text.val());
if (!text) {
$status.addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var lines = extras();
var held = collected();
var wikitext = ["# " + text].concat(lines.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType,
sectionOrdinal, definitionLevel, text, lines), warnings: [] };
},
$save,
$status,
// Drawn fresh every time: redrawPreviews() calls this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
var $edit = $("<a href='#' class='dsb-quick-link'></a>")
.text("Vê maneyê biguhêre")
.on("click", function (e) {
e.preventDefault();
openNewSenseRow(held);
});
renderWikitext(wikitext).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
var $item = parsedSenseItem(html, 1);
$pending.empty().append($item.length ? $item.contents() :
$("<span></span>").text(text));
$pending.append($("<div class='dsb-new-sense-actions'></div>").append($edit));
announceContent($pending);
}, function () {
$pending.empty().text(text)
.append($("<div class='dsb-new-sense-actions'></div>").append($edit));
});
return function () {
$pending.remove();
};
},
{ key: "newSense:" + definitionLevel.join("."), sensePath: definitionLevel,
senseLanguage: language }
);
});
$box.append($text, $lists, $quotes, $picker,
$("<div class='dsb-form-actions'></div>").append($add, $cancel, $status));
$row.append($box);
$li.after($row);
$text.trigger("focus");
if (onResize) {
onResize();
}
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: "Maneyê biguhêre", build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
mhqnul5vmvyis4vji0nu20378jzkbwr
6535001
6534999
2026-09-20T19:52:55Z
Wikihez
7344
DefSideBoxes: drop a resize hook the sense panel does not have
6535001
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense) {
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
return (v && v !== originalGloss) ? v : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
// `state` reopens a sense already queued: {text, lists, quotes} as the box last held them.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
openQueueBox();
state = state || { text: "", lists: {}, quotes: [] };
var lists = {};
var quotes = state.quotes.slice();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $box = $("<div class='dsb-new-sense'></div>");
var $text = $("<textarea class='dsb-input' rows='2'></textarea>")
.attr("placeholder", "Maneya nû binivîse…").val(state.text);
var $lists = $("<div class='dsb-new-sense-lists'></div>");
var $quotes = $("<div class='dsb-new-sense-quotes'></div>");
var $picker = $("<div class='dsb-new-sense-picker'></div>");
var $add = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
$quickRow.data("dsbNewSenseRow", $row);
function drawQuote(line) {
$quotes.append($("<div class='dsb-new-sense-quote'></div>").text(line));
}
quotes.forEach(function (extra) {
drawQuote("#" + extra);
});
// "#:" lines in senseGroups' own order, then the quotations in the order they were added --
// the same order the wizard writes them in.
function extras() {
var out = [];
SENSE_GROUPS.forEach(function (group) {
var entry = lists[group.key];
var words = entry ? entry.values() : [];
if (words.length) {
out.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
return out.concat(quotes);
}
function collected() {
var held = {};
Object.keys(lists).forEach(function (key) {
held[key] = lists[key].values();
});
return { text: $.trim($text.val()), lists: held, quotes: quotes.slice() };
}
function addList(group, words) {
if (lists[group.key]) {
lists[group.key].$element.find("input").first().trigger("focus");
return;
}
var field = buildWordListField(group.label + "…");
if (words && words.length) {
field.setValues(words);
}
lists[group.key] = field;
$lists.append($("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text(group.label), field.$element));
}
// The form is opened on this sense, since a row that is not on the page has nothing for
// add-examples to attach to; only the line it builds is kept, never its page transform.
function addQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
quotes.push(job.line.replace(/^#+/, ""));
drawQuote(job.line);
job.done();
});
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// The same shape as a saved sense's row: the one action that is used every time sits in the
// open, the word lists behind "Hevmane û Bêhtir". There is no "Maneyê biguhêre" here -- the
// box above it is the gloss.
var $rowMenu = $("<div class='dsb-more-menu'></div>").hide();
var $rowArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $rowToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($rowArrow);
$rowToggle.on("click", function (e) {
e.preventDefault();
$rowMenu.slideToggle(function () {
$rowArrow.text($rowMenu.is(":visible") ? "▾" : "▸");
});
});
// senseGroups' own order, the order the "#:" lines are written in, as in the sense menu.
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).forEach(function (group) {
$rowMenu.append($("<a href='#' class='dsb-quick-link'></a>").text("+ " + group.label)
.on("click", function (e) {
e.preventDefault();
$rowMenu.slideUp(function () {
$rowArrow.text("▸");
});
addList(group);
}));
});
$picker.append($("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin")
.on("click", function (e) {
e.preventDefault();
addQuotation();
}), $rowToggle, $rowMenu);
SENSE_GROUPS.forEach(function (group) {
var words = state.lists[group.key];
if (words && words.length) {
addList(group, words);
}
});
$cancel.on("click", function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
});
$add.on("click", function () {
var text = $.trim($text.val());
if (!text) {
$status.addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var lines = extras();
var held = collected();
var wikitext = ["# " + text].concat(lines.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType,
sectionOrdinal, definitionLevel, text, lines), warnings: [] };
},
$save,
$status,
// Drawn fresh every time: redrawPreviews() calls this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
var $edit = $("<a href='#' class='dsb-quick-link'></a>")
.text("Vê maneyê biguhêre")
.on("click", function (e) {
e.preventDefault();
openNewSenseRow(held);
});
renderWikitext(wikitext).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
var $item = parsedSenseItem(html, 1);
$pending.empty().append($item.length ? $item.contents() :
$("<span></span>").text(text));
$pending.append($("<div class='dsb-new-sense-actions'></div>").append($edit));
announceContent($pending);
}, function () {
$pending.empty().text(text)
.append($("<div class='dsb-new-sense-actions'></div>").append($edit));
});
return function () {
$pending.remove();
};
},
{ key: "newSense:" + definitionLevel.join("."), sensePath: definitionLevel,
senseLanguage: language }
);
});
$box.append($text, $lists, $quotes, $picker,
$("<div class='dsb-form-actions'></div>").append($add, $cancel, $status));
$row.append($box);
$li.after($row);
$text.trigger("focus");
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: "Maneyê biguhêre", build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
4c0zidf9cjvsuj96c70f3gl7wudoalc
6535002
6535001
2026-09-20T19:55:29Z
Wikihez
7344
DefSideBoxes: reopening a queued new sense takes it out of the queue
6535002
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense) {
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
return (v && v !== originalGloss) ? v : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
var newSenseKey = "newSense:" + definitionLevel.join(".");
// Reopening one takes it back out of the queue: its rendered row belongs to that entry, and
// leaving the entry in place would show the sense twice, once pending and once being written.
function dropQueuedNewSense() {
dsbQueue.entries.slice().forEach(function (entry) {
if (entry.key === newSenseKey) {
dropQueueEntry(entry);
}
});
}
// `state` reopens a sense already queued: {text, lists, quotes} as the box last held them.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
if (state) {
dropQueuedNewSense();
}
openQueueBox();
state = state || { text: "", lists: {}, quotes: [] };
var lists = {};
var quotes = state.quotes.slice();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $box = $("<div class='dsb-new-sense'></div>");
var $text = $("<textarea class='dsb-input' rows='2'></textarea>")
.attr("placeholder", "Maneya nû binivîse…").val(state.text);
var $lists = $("<div class='dsb-new-sense-lists'></div>");
var $quotes = $("<div class='dsb-new-sense-quotes'></div>");
var $picker = $("<div class='dsb-new-sense-picker'></div>");
var $add = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
$quickRow.data("dsbNewSenseRow", $row);
function drawQuote(line) {
$quotes.append($("<div class='dsb-new-sense-quote'></div>").text(line));
}
quotes.forEach(function (extra) {
drawQuote("#" + extra);
});
// "#:" lines in senseGroups' own order, then the quotations in the order they were added --
// the same order the wizard writes them in.
function extras() {
var out = [];
SENSE_GROUPS.forEach(function (group) {
var entry = lists[group.key];
var words = entry ? entry.values() : [];
if (words.length) {
out.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
return out.concat(quotes);
}
function collected() {
var held = {};
Object.keys(lists).forEach(function (key) {
held[key] = lists[key].values();
});
return { text: $.trim($text.val()), lists: held, quotes: quotes.slice() };
}
function addList(group, words) {
if (lists[group.key]) {
lists[group.key].$element.find("input").first().trigger("focus");
return;
}
var field = buildWordListField(group.label + "…");
if (words && words.length) {
field.setValues(words);
}
lists[group.key] = field;
$lists.append($("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text(group.label), field.$element));
}
// The form is opened on this sense, since a row that is not on the page has nothing for
// add-examples to attach to; only the line it builds is kept, never its page transform.
function addQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
quotes.push(job.line.replace(/^#+/, ""));
drawQuote(job.line);
job.done();
});
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// The same shape as a saved sense's row: the one action that is used every time sits in the
// open, the word lists behind "Hevmane û Bêhtir". There is no "Maneyê biguhêre" here -- the
// box above it is the gloss.
var $rowMenu = $("<div class='dsb-more-menu'></div>").hide();
var $rowArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $rowToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($rowArrow);
$rowToggle.on("click", function (e) {
e.preventDefault();
$rowMenu.slideToggle(function () {
$rowArrow.text($rowMenu.is(":visible") ? "▾" : "▸");
});
});
// senseGroups' own order, the order the "#:" lines are written in, as in the sense menu.
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).forEach(function (group) {
$rowMenu.append($("<a href='#' class='dsb-quick-link'></a>").text("+ " + group.label)
.on("click", function (e) {
e.preventDefault();
$rowMenu.slideUp(function () {
$rowArrow.text("▸");
});
addList(group);
}));
});
$picker.append($("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin")
.on("click", function (e) {
e.preventDefault();
addQuotation();
}), $rowToggle, $rowMenu);
SENSE_GROUPS.forEach(function (group) {
var words = state.lists[group.key];
if (words && words.length) {
addList(group, words);
}
});
$cancel.on("click", function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
});
$add.on("click", function () {
var text = $.trim($text.val());
if (!text) {
$status.addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var lines = extras();
var held = collected();
var wikitext = ["# " + text].concat(lines.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType,
sectionOrdinal, definitionLevel, text, lines), warnings: [] };
},
$save,
$status,
// Drawn fresh every time: redrawPreviews() calls this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
var $edit = $("<a href='#' class='dsb-quick-link'></a>")
.text("Vê maneyê biguhêre")
.on("click", function (e) {
e.preventDefault();
openNewSenseRow(held);
});
renderWikitext(wikitext).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
var $item = parsedSenseItem(html, 1);
$pending.empty().append($item.length ? $item.contents() :
$("<span></span>").text(text));
$pending.append($("<div class='dsb-new-sense-actions'></div>").append($edit));
announceContent($pending);
}, function () {
$pending.empty().text(text)
.append($("<div class='dsb-new-sense-actions'></div>").append($edit));
});
return function () {
$pending.remove();
};
},
{ key: newSenseKey, sensePath: definitionLevel, senseLanguage: language }
);
});
$box.append($text, $lists, $quotes, $picker,
$("<div class='dsb-form-actions'></div>").append($add, $cancel, $status));
$row.append($box);
$li.after($row);
$text.trigger("focus");
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: "Maneyê biguhêre", build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
a4urq03i990rzrh4mdtq2e8p58bmacw
6535003
6535002
2026-09-20T19:57:32Z
Wikihez
7344
DefSideBoxes: the pending sense row reads parsedSenseItem's own shape
6535003
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense) {
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
return (v && v !== originalGloss) ? v : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
var newSenseKey = "newSense:" + definitionLevel.join(".");
// Reopening one takes it back out of the queue: its rendered row belongs to that entry, and
// leaving the entry in place would show the sense twice, once pending and once being written.
function dropQueuedNewSense() {
dsbQueue.entries.slice().forEach(function (entry) {
if (entry.key === newSenseKey) {
dropQueueEntry(entry);
}
});
}
// `state` reopens a sense already queued: {text, lists, quotes} as the box last held them.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
if (state) {
dropQueuedNewSense();
}
openQueueBox();
state = state || { text: "", lists: {}, quotes: [] };
var lists = {};
var quotes = state.quotes.slice();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $box = $("<div class='dsb-new-sense'></div>");
var $text = $("<textarea class='dsb-input' rows='2'></textarea>")
.attr("placeholder", "Maneya nû binivîse…").val(state.text);
var $lists = $("<div class='dsb-new-sense-lists'></div>");
var $quotes = $("<div class='dsb-new-sense-quotes'></div>");
var $picker = $("<div class='dsb-new-sense-picker'></div>");
var $add = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
$quickRow.data("dsbNewSenseRow", $row);
function drawQuote(line) {
$quotes.append($("<div class='dsb-new-sense-quote'></div>").text(line));
}
quotes.forEach(function (extra) {
drawQuote("#" + extra);
});
// "#:" lines in senseGroups' own order, then the quotations in the order they were added --
// the same order the wizard writes them in.
function extras() {
var out = [];
SENSE_GROUPS.forEach(function (group) {
var entry = lists[group.key];
var words = entry ? entry.values() : [];
if (words.length) {
out.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
return out.concat(quotes);
}
function collected() {
var held = {};
Object.keys(lists).forEach(function (key) {
held[key] = lists[key].values();
});
return { text: $.trim($text.val()), lists: held, quotes: quotes.slice() };
}
function addList(group, words) {
if (lists[group.key]) {
lists[group.key].$element.find("input").first().trigger("focus");
return;
}
var field = buildWordListField(group.label + "…");
if (words && words.length) {
field.setValues(words);
}
lists[group.key] = field;
$lists.append($("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text(group.label), field.$element));
}
// The form is opened on this sense, since a row that is not on the page has nothing for
// add-examples to attach to; only the line it builds is kept, never its page transform.
function addQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
quotes.push(job.line.replace(/^#+/, ""));
drawQuote(job.line);
job.done();
});
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// The same shape as a saved sense's row: the one action that is used every time sits in the
// open, the word lists behind "Hevmane û Bêhtir". There is no "Maneyê biguhêre" here -- the
// box above it is the gloss.
var $rowMenu = $("<div class='dsb-more-menu'></div>").hide();
var $rowArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $rowToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($rowArrow);
$rowToggle.on("click", function (e) {
e.preventDefault();
$rowMenu.slideToggle(function () {
$rowArrow.text($rowMenu.is(":visible") ? "▾" : "▸");
});
});
// senseGroups' own order, the order the "#:" lines are written in, as in the sense menu.
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).forEach(function (group) {
$rowMenu.append($("<a href='#' class='dsb-quick-link'></a>").text("+ " + group.label)
.on("click", function (e) {
e.preventDefault();
$rowMenu.slideUp(function () {
$rowArrow.text("▸");
});
addList(group);
}));
});
$picker.append($("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin")
.on("click", function (e) {
e.preventDefault();
addQuotation();
}), $rowToggle, $rowMenu);
SENSE_GROUPS.forEach(function (group) {
var words = state.lists[group.key];
if (words && words.length) {
addList(group, words);
}
});
$cancel.on("click", function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
});
$add.on("click", function () {
var text = $.trim($text.val());
if (!text) {
$status.addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var lines = extras();
var held = collected();
var wikitext = ["# " + text].concat(lines.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType,
sectionOrdinal, definitionLevel, text, lines), warnings: [] };
},
$save,
$status,
// Drawn fresh every time: redrawPreviews() calls this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
var $edit = $("<a href='#' class='dsb-quick-link'></a>")
.text("Vê maneyê biguhêre")
.on("click", function (e) {
e.preventDefault();
openNewSenseRow(held);
});
renderWikitext(wikitext).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
// parsedSenseItem returns {html, attrs}, the sense's own markup without the
// <li> around it -- this row is that <li>.
$pending.empty().html(parsedSenseItem(html, 1).html)
.append($("<div class='dsb-new-sense-actions'></div>").append($edit));
announceContent($pending);
}, function () {
$pending.empty().text(text)
.append($("<div class='dsb-new-sense-actions'></div>").append($edit));
});
return function () {
$pending.remove();
};
},
{ key: newSenseKey, sensePath: definitionLevel, senseLanguage: language }
);
});
$box.append($text, $lists, $quotes, $picker,
$("<div class='dsb-form-actions'></div>").append($add, $cancel, $status));
$row.append($box);
$li.after($row);
$text.trigger("focus");
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: "Maneyê biguhêre", build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
ouvln3woicaxq25upp9ckbpxnn3xsq6
6535004
6535003
2026-09-20T20:02:19Z
Wikihez
7344
DefSideBoxes: the pending sense gets a pencil and Oto-lînk bike
6535004
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var GLOSS_LABEL = "Maneyê biguhêre";
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense) {
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
return (v && v !== originalGloss) ? v : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
var newSenseKey = "newSense:" + definitionLevel.join(".");
// Reopening one takes it back out of the queue: its rendered row belongs to that entry, and
// leaving the entry in place would show the sense twice, once pending and once being written.
function dropQueuedNewSense() {
dsbQueue.entries.slice().forEach(function (entry) {
if (entry.key === newSenseKey) {
dropQueueEntry(entry);
}
});
}
// `state` reopens a sense already queued: {text, lists, quotes} as the box last held them.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
if (state) {
dropQueuedNewSense();
}
openQueueBox();
state = state || { text: "", lists: {}, quotes: [] };
var lists = {};
var quotes = state.quotes.slice();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $box = $("<div class='dsb-new-sense'></div>");
var $text = $("<textarea class='dsb-input' rows='2'></textarea>")
.attr("placeholder", "Maneya nû binivîse…").val(state.text);
var $lists = $("<div class='dsb-new-sense-lists'></div>");
var $quotes = $("<div class='dsb-new-sense-quotes'></div>");
var $picker = $("<div class='dsb-new-sense-picker'></div>");
var $add = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
// The same button the gloss box has: a definition typed here wants linking just as much.
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>");
$autoLink.on("click", function () {
var text = $.trim($text.val());
if (!text) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(text, "", function (linked, _izah, error) {
$text.val(linked);
$autoLink.prop("disabled", false);
$status.text(error || "");
if (error) {
$status.addClass("dsb-status-error");
}
});
});
$quickRow.data("dsbNewSenseRow", $row);
function drawQuote(line) {
$quotes.append($("<div class='dsb-new-sense-quote'></div>").text(line));
}
quotes.forEach(function (extra) {
drawQuote("#" + extra);
});
// "#:" lines in senseGroups' own order, then the quotations in the order they were added --
// the same order the wizard writes them in.
function extras() {
var out = [];
SENSE_GROUPS.forEach(function (group) {
var entry = lists[group.key];
var words = entry ? entry.values() : [];
if (words.length) {
out.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
return out.concat(quotes);
}
function collected() {
var held = {};
Object.keys(lists).forEach(function (key) {
held[key] = lists[key].values();
});
return { text: $.trim($text.val()), lists: held, quotes: quotes.slice() };
}
function addList(group, words) {
if (lists[group.key]) {
lists[group.key].$element.find("input").first().trigger("focus");
return;
}
var field = buildWordListField(group.label + "…");
if (words && words.length) {
field.setValues(words);
}
lists[group.key] = field;
$lists.append($("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text(group.label), field.$element));
}
// The form is opened on this sense, since a row that is not on the page has nothing for
// add-examples to attach to; only the line it builds is kept, never its page transform.
function addQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
quotes.push(job.line.replace(/^#+/, ""));
drawQuote(job.line);
job.done();
});
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// The same shape as a saved sense's row: the one action that is used every time sits in the
// open, the word lists behind "Hevmane û Bêhtir". There is no "Maneyê biguhêre" here -- the
// box above it is the gloss.
var $rowMenu = $("<div class='dsb-more-menu'></div>").hide();
var $rowArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $rowToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($rowArrow);
$rowToggle.on("click", function (e) {
e.preventDefault();
$rowMenu.slideToggle(function () {
$rowArrow.text($rowMenu.is(":visible") ? "▾" : "▸");
});
});
// senseGroups' own order, the order the "#:" lines are written in, as in the sense menu.
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).forEach(function (group) {
$rowMenu.append($("<a href='#' class='dsb-quick-link'></a>").text("+ " + group.label)
.on("click", function (e) {
e.preventDefault();
$rowMenu.slideUp(function () {
$rowArrow.text("▸");
});
addList(group);
}));
});
$picker.append($("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin")
.on("click", function (e) {
e.preventDefault();
addQuotation();
}), $rowToggle, $rowMenu);
SENSE_GROUPS.forEach(function (group) {
var words = state.lists[group.key];
if (words && words.length) {
addList(group, words);
}
});
$cancel.on("click", function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
});
$add.on("click", function () {
var text = $.trim($text.val());
if (!text) {
$status.addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var lines = extras();
var held = collected();
var wikitext = ["# " + text].concat(lines.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType,
sectionOrdinal, definitionLevel, text, lines), warnings: [] };
},
$save,
$status,
// Drawn fresh every time: redrawPreviews() calls this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
// The pencil every other editable row on the page carries, with the wording the
// sense panel already uses for the same action.
var $edit = $("<span class='dsb-example-toolbar'></span>").append(
$("<a href='#' class='dsb-toggle'></a>")
.attr("title", GLOSS_LABEL)
.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
openNewSenseRow(held);
}));
renderWikitext(wikitext).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
// parsedSenseItem returns {html, attrs}, the sense's own markup without the
// <li> around it -- this row is that <li>.
$pending.empty().html(parsedSenseItem(html, 1).html).append($edit);
announceContent($pending);
}, function () {
$pending.empty().text(text).append($edit);
});
return function () {
$pending.remove();
};
},
{ key: newSenseKey, sensePath: definitionLevel, senseLanguage: language }
);
});
$box.append($text, $lists, $quotes, $picker,
$("<div class='dsb-form-actions'></div>").append($add, $cancel, $autoLink, $status));
$row.append($box);
$li.after($row);
$text.trigger("focus");
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: GLOSS_LABEL, build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
77e07ffskbx7jryeufsniog32oefxeq
6535012
6535004
2026-09-20T20:13:01Z
Wikihez
7344
DefSideBoxes: the pending sense renders in page context, keeps its pencil visible, and the example form opens beside it
6535012
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var GLOSS_LABEL = "Maneyê biguhêre";
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense) {
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
return (v && v !== originalGloss) ? v : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
var newSenseKey = "newSense:" + definitionLevel.join(".");
// Reopening one takes it back out of the queue: its rendered row belongs to that entry, and
// leaving the entry in place would show the sense twice, once pending and once being written.
function dropQueuedNewSense() {
dsbQueue.entries.slice().forEach(function (entry) {
if (entry.key === newSenseKey) {
dropQueueEntry(entry);
}
});
}
// `state` reopens a sense already queued: {text, lists, quotes} as the box last held them.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
if (state) {
dropQueuedNewSense();
}
openQueueBox();
state = state || { text: "", lists: {}, quotes: [] };
var lists = {};
var quotes = state.quotes.slice();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $box = $("<div class='dsb-new-sense'></div>");
var $text = $("<textarea class='dsb-input' rows='2'></textarea>")
.attr("placeholder", "Maneya nû binivîse…").val(state.text);
var $lists = $("<div class='dsb-new-sense-lists'></div>");
var $quotes = $("<div class='dsb-new-sense-quotes'></div>");
var $picker = $("<div class='dsb-new-sense-picker'></div>");
var $add = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
// The same button the gloss box has: a definition typed here wants linking just as much.
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>");
$autoLink.on("click", function () {
var text = $.trim($text.val());
if (!text) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(text, "", function (linked, _izah, error) {
$text.val(linked);
$autoLink.prop("disabled", false);
$status.text(error || "");
if (error) {
$status.addClass("dsb-status-error");
}
});
});
$quickRow.data("dsbNewSenseRow", $row);
function drawQuote(line) {
$quotes.append($("<div class='dsb-new-sense-quote'></div>").text(line));
}
quotes.forEach(function (extra) {
drawQuote("#" + extra);
});
// "#:" lines in senseGroups' own order, then the quotations in the order they were added --
// the same order the wizard writes them in.
function extras() {
var out = [];
SENSE_GROUPS.forEach(function (group) {
var entry = lists[group.key];
var words = entry ? entry.values() : [];
if (words.length) {
out.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
return out.concat(quotes);
}
function collected() {
var held = {};
Object.keys(lists).forEach(function (key) {
held[key] = lists[key].values();
});
return { text: $.trim($text.val()), lists: held, quotes: quotes.slice() };
}
function addList(group, words) {
if (lists[group.key]) {
lists[group.key].$element.find("input").first().trigger("focus");
return;
}
var field = buildWordListField(group.label + "…");
if (words && words.length) {
field.setValues(words);
}
lists[group.key] = field;
$lists.append($("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text(group.label), field.$element));
}
// The form is opened on this sense, since a row that is not on the page has nothing for
// add-examples to attach to; only the line it builds is kept, never its page transform.
function addQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
quotes.push(job.line.replace(/^#+/, ""));
drawQuote(job.line);
job.done();
});
var drive = wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
return;
}
// It attaches itself inside the sense it was opened on, which is not where this row is.
var $form = $li.find(".add-example-form").first();
if ($form.length) {
$quotes.after($form.closest(".oo-ui-panelLayout, .add-example-form"));
}
}
// The same shape as a saved sense's row: the one action that is used every time sits in the
// open, the word lists behind "Hevmane û Bêhtir". There is no "Maneyê biguhêre" here -- the
// box above it is the gloss.
var $rowMenu = $("<div class='dsb-more-menu'></div>").hide();
var $rowArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $rowToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($rowArrow);
$rowToggle.on("click", function (e) {
e.preventDefault();
$rowMenu.slideToggle(function () {
$rowArrow.text($rowMenu.is(":visible") ? "▾" : "▸");
});
});
// senseGroups' own order, the order the "#:" lines are written in, as in the sense menu.
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).forEach(function (group) {
$rowMenu.append($("<a href='#' class='dsb-quick-link'></a>").text("+ " + group.label)
.on("click", function (e) {
e.preventDefault();
$rowMenu.slideUp(function () {
$rowArrow.text("▸");
});
addList(group);
}));
});
$picker.append($("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin")
.on("click", function (e) {
e.preventDefault();
addQuotation();
}), $rowToggle, $rowMenu);
SENSE_GROUPS.forEach(function (group) {
var words = state.lists[group.key];
if (words && words.length) {
addList(group, words);
}
});
$cancel.on("click", function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
});
$add.on("click", function () {
var text = $.trim($text.val());
if (!text) {
$status.addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var lines = extras();
var held = collected();
var wikitext = ["# " + text].concat(lines.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType,
sectionOrdinal, definitionLevel, text, lines), warnings: [] };
},
$save,
$status,
// Drawn fresh every time: redrawPreviews() calls this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
// The pencil every other editable row on the page carries, with the wording the
// sense panel already uses for the same action.
var $edit = $("<span class='dsb-example-toolbar'></span>").append(
$("<a href='#' class='dsb-toggle'></a>")
.attr("title", GLOSS_LABEL)
.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
openNewSenseRow(held);
}));
renderWikitext(wikitext).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
// parsedSenseItem returns {html, attrs}, the sense's own markup without the
// <li> around it -- this row is that <li>.
$pending.empty()
.append($("<div class='mw-parser-output'></div>")
.html(parsedSenseItem(html, 1).html))
.append($edit);
announceContent($pending);
}, function () {
$pending.empty().text(text).append($edit);
});
return function () {
$pending.remove();
};
},
{ key: newSenseKey, sensePath: definitionLevel, senseLanguage: language }
);
});
$box.append($text, $lists, $quotes, $picker,
$("<div class='dsb-form-actions'></div>").append($add, $cancel, $autoLink, $status));
$row.append($box);
$li.after($row);
$text.trigger("focus");
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: GLOSS_LABEL, build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
oetjugawysrfmiv9m8ythzr9ad76j9x
6535014
6535012
2026-09-20T20:22:06Z
Wikihez
7344
DefSideBoxes: the new-sense row gets every field, an inline pencil, and cancel that only closes
6535014
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var GLOSS_LABEL = "Maneyê biguhêre";
// The leading calls a new sense can carry, with the template each one writes.
var NEW_SENSE_LEADING = [
{ tpl: "e", label: "Etîket" },
{ tpl: "K", label: "Kategorî" },
{ tpl: "maneid", label: "Maneid", single: true }
];
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense) {
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
return (v && v !== originalGloss) ? v : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
var newSenseKey = "newSense:" + definitionLevel.join(".");
function queuedNewSense() {
var found = null;
dsbQueue.entries.forEach(function (entry) {
if (entry.key === newSenseKey) {
found = entry;
}
});
return found;
}
// "Maneyê rake" on the row: the entry goes, and with it the rendered sense.
function dropQueuedNewSense() {
dsbQueue.entries.slice().forEach(function (entry) {
if (entry.key === newSenseKey) {
dropQueueEntry(entry);
}
});
}
// `state` reopens a sense already queued: {text, lists, quotes} as the box last held them.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
if (state) {
// The rendered row belongs to the queue entry; hide it while the box is open, but leave
// the entry alone so cancelling can put the row back.
$li.nextAll("li.dsb-new-sense-pending").first().remove();
}
openQueueBox();
state = state || { text: "", lists: {}, quotes: [] };
var lists = {};
var leading = {}; // tplKey -> [values], written in front of the gloss
var quotes = state.quotes.slice();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $box = $("<div class='dsb-new-sense'></div>");
var $text = $("<textarea class='dsb-input' rows='2'></textarea>")
.attr("placeholder", "Maneya nû binivîse…").val(state.text);
var $lists = $("<div class='dsb-new-sense-lists'></div>");
var $quotes = $("<div class='dsb-new-sense-quotes'></div>");
var $picker = $("<div class='dsb-new-sense-picker'></div>");
var $add = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
// The same button the gloss box has: a definition typed here wants linking just as much.
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>");
$autoLink.on("click", function () {
var text = $.trim($text.val());
if (!text) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(text, "", function (linked, _izah, error) {
$text.val(linked);
$autoLink.prop("disabled", false);
$status.text(error || "");
if (error) {
$status.addClass("dsb-status-error");
}
});
});
$quickRow.data("dsbNewSenseRow", $row);
function drawQuote(line) {
$quotes.append($("<div class='dsb-new-sense-quote'></div>").text(line));
}
quotes.forEach(function (extra) {
drawQuote("#" + extra);
});
// {{maneid|…}} {{e|…}} {{K|…}}, in LEADING_TPL_ORDER, ahead of the definition itself -- the
// same place insertLeadingSenseTpl puts them on a sense that already exists.
function leadingCalls() {
return LEADING_TPL_ORDER.map(function (key) {
var values = leading[key] || [];
return values.length ? "{{" + key + "|" + language + "|" + values.join("|") + "}}" : null;
}).filter(Boolean).join(" ");
}
function definitionLine() {
var calls = leadingCalls();
return (calls ? calls + " " : "") + $.trim($text.val());
}
// "#:" lines in senseGroups' own order, then the quotations in the order they were added --
// the same order the wizard writes them in.
function extras() {
var out = [];
SENSE_GROUPS.forEach(function (group) {
var entry = lists[group.key];
var words = entry ? entry.values() : [];
if (words.length) {
out.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
return out.concat(quotes);
}
function collected() {
var held = {};
Object.keys(lists).forEach(function (key) {
held[key] = lists[key].values();
});
var leadingHeld = {};
Object.keys(leading).forEach(function (key) {
leadingHeld[key] = leading[key].slice();
});
return { text: $.trim($text.val()), lists: held, leading: leadingHeld,
quotes: quotes.slice() };
}
// One box for a leading call: etîket and kategorî take a list, maneid a single value.
function addLeadingField(def, values) {
if (leading[def.tpl]) {
return;
}
leading[def.tpl] = (values || []).slice();
var $wrap = $("<div class='dsb-adv-group'></div>")
.append($("<div class='dsb-adv-label'></div>").text(def.label));
if (def.single) {
var $input = $("<input type='text' class='dsb-input'>")
.attr("placeholder", def.label + "…").val((values || [])[0] || "");
$input.on("input", function () {
var v = $.trim($input.val());
leading[def.tpl] = v ? [v] : [];
});
$wrap.append($input);
} else {
var field = buildWordListField(def.label + "…");
if (values && values.length) {
field.setValues(values);
}
// Read back on save, like the word lists: the field owns its own rows.
leading[def.tpl] = values ? values.slice() : [];
field.$element.on("input change", function () {
leading[def.tpl] = field.values();
});
$wrap.append(field.$element);
}
$lists.append($wrap);
}
function addList(group, words) {
if (lists[group.key]) {
lists[group.key].$element.find("input").first().trigger("focus");
return;
}
var field = buildWordListField(group.label + "…");
if (words && words.length) {
field.setValues(words);
}
lists[group.key] = field;
$lists.append($("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text(group.label), field.$element));
}
// The form is opened on this sense, since a row that is not on the page has nothing for
// add-examples to attach to; only the line it builds is kept, never its page transform.
function addQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
quotes.push(job.line.replace(/^#+/, ""));
drawQuote(job.line);
job.done();
});
var drive = wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
return;
}
// It attaches itself inside the sense it was opened on, which is not where this row is:
// its title sits in that sense's example list, the form panel after it. Both move.
var $title = $li.find(".add-example-form-title").first().closest("li");
var $form = $li.find(".add-example-form").first();
if ($title.length) {
$quotes.after($title);
}
if ($form.length) {
($title.length ? $title : $quotes).after($form);
}
}
// The same shape as a saved sense's row: the one action that is used every time sits in the
// open, the word lists behind "Hevmane û Bêhtir". There is no "Maneyê biguhêre" here -- the
// box above it is the gloss.
var $rowMenu = $("<div class='dsb-more-menu'></div>").hide();
var $rowArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $rowToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($rowArrow);
$rowToggle.on("click", function (e) {
e.preventDefault();
$rowMenu.slideToggle(function () {
$rowArrow.text($rowMenu.is(":visible") ? "▾" : "▸");
});
});
function menuItem(label, open) {
return $("<a href='#' class='dsb-quick-link'></a>").text("+ " + label)
.on("click", function (e) {
e.preventDefault();
$rowMenu.slideUp(function () {
$rowArrow.text("▸");
});
open();
});
}
// The same three groups the sense menu has: what labels the sense, then its word lists.
NEW_SENSE_LEADING.forEach(function (def) {
$rowMenu.append(menuItem(def.label, function () {
addLeadingField(def);
}));
});
// senseGroups' own order, the order the "#:" lines are written in, as in the sense menu.
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).forEach(function (group) {
$rowMenu.append(menuItem(group.label, function () {
addList(group);
}));
});
$picker.append($("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin")
.on("click", function (e) {
e.preventDefault();
addQuotation();
}), $rowToggle, $rowMenu);
NEW_SENSE_LEADING.forEach(function (def) {
var values = (state.leading || {})[def.tpl];
if (values && values.length) {
addLeadingField(def, values);
}
});
SENSE_GROUPS.forEach(function (group) {
var words = state.lists[group.key];
if (words && words.length) {
addList(group, words);
}
});
// Closing is not deleting: a sense already queued keeps its entry and its row, and
// "Maneyê rake" below is what takes it out of the queue.
$cancel.on("click", function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
if (queuedNewSense()) {
redrawPreviews();
}
hideQueueIfIdle();
});
var $drop = $("<a href='#' class='dsb-quick-link dsb-field-delete'></a>")
.text("Maneyê rake")
.on("click", function (e) {
e.preventDefault();
dropQueuedNewSense();
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
});
$add.on("click", function () {
var text = definitionLine();
if (!$.trim($text.val())) {
$status.addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var lines = extras();
var held = collected();
var wikitext = ["# " + text].concat(lines.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType,
sectionOrdinal, definitionLevel, text, lines), warnings: [] };
},
$save,
$status,
// Drawn fresh every time: redrawPreviews() calls this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
// The pencil a sense carries, in the same place: at the start of the row, not
// after its content, where a block of rendered wikitext pushes it onto a line of
// its own.
var $edit = $("<span class='dsb-toolbar'></span>").append(
$("<a href='#' class='dsb-toggle'></a>")
.attr("title", GLOSS_LABEL)
.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
openNewSenseRow(held);
}));
renderWikitext(wikitext).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
// parsedSenseItem returns {html, attrs}, the sense's own markup without the
// <li> around it -- this row is that <li>.
$pending.empty().append($edit,
$("<div class='mw-parser-output'></div>")
.html(parsedSenseItem(html, 1).html));
announceContent($pending);
}, function () {
$pending.empty().append($edit, $("<span></span>").text(text));
});
return function () {
$pending.remove();
};
},
{ key: newSenseKey, sensePath: definitionLevel, senseLanguage: language }
);
});
$box.append($text, $lists, $quotes, $picker,
$("<div class='dsb-form-actions'></div>")
.append($add, $cancel, $autoLink, $status, $drop));
$row.append($box);
$li.after($row);
$text.trigger("focus");
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: GLOSS_LABEL, build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
gwwvnxua7jgs4819n5y6gbb6aec0062
6535017
6535014
2026-09-20T20:23:59Z
Wikihez
7344
DefSideBoxes: the pending sense's pencil sits on its first line
6535017
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var GLOSS_LABEL = "Maneyê biguhêre";
// The leading calls a new sense can carry, with the template each one writes.
var NEW_SENSE_LEADING = [
{ tpl: "e", label: "Etîket" },
{ tpl: "K", label: "Kategorî" },
{ tpl: "maneid", label: "Maneid", single: true }
];
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense) {
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
return (v && v !== originalGloss) ? v : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
var newSenseKey = "newSense:" + definitionLevel.join(".");
function queuedNewSense() {
var found = null;
dsbQueue.entries.forEach(function (entry) {
if (entry.key === newSenseKey) {
found = entry;
}
});
return found;
}
// "Maneyê rake" on the row: the entry goes, and with it the rendered sense.
function dropQueuedNewSense() {
dsbQueue.entries.slice().forEach(function (entry) {
if (entry.key === newSenseKey) {
dropQueueEntry(entry);
}
});
}
// `state` reopens a sense already queued: {text, lists, quotes} as the box last held them.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
if (state) {
// The rendered row belongs to the queue entry; hide it while the box is open, but leave
// the entry alone so cancelling can put the row back.
$li.nextAll("li.dsb-new-sense-pending").first().remove();
}
openQueueBox();
state = state || { text: "", lists: {}, quotes: [] };
var lists = {};
var leading = {}; // tplKey -> [values], written in front of the gloss
var quotes = state.quotes.slice();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $box = $("<div class='dsb-new-sense'></div>");
var $text = $("<textarea class='dsb-input' rows='2'></textarea>")
.attr("placeholder", "Maneya nû binivîse…").val(state.text);
var $lists = $("<div class='dsb-new-sense-lists'></div>");
var $quotes = $("<div class='dsb-new-sense-quotes'></div>");
var $picker = $("<div class='dsb-new-sense-picker'></div>");
var $add = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
// The same button the gloss box has: a definition typed here wants linking just as much.
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>");
$autoLink.on("click", function () {
var text = $.trim($text.val());
if (!text) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(text, "", function (linked, _izah, error) {
$text.val(linked);
$autoLink.prop("disabled", false);
$status.text(error || "");
if (error) {
$status.addClass("dsb-status-error");
}
});
});
$quickRow.data("dsbNewSenseRow", $row);
function drawQuote(line) {
$quotes.append($("<div class='dsb-new-sense-quote'></div>").text(line));
}
quotes.forEach(function (extra) {
drawQuote("#" + extra);
});
// {{maneid|…}} {{e|…}} {{K|…}}, in LEADING_TPL_ORDER, ahead of the definition itself -- the
// same place insertLeadingSenseTpl puts them on a sense that already exists.
function leadingCalls() {
return LEADING_TPL_ORDER.map(function (key) {
var values = leading[key] || [];
return values.length ? "{{" + key + "|" + language + "|" + values.join("|") + "}}" : null;
}).filter(Boolean).join(" ");
}
function definitionLine() {
var calls = leadingCalls();
return (calls ? calls + " " : "") + $.trim($text.val());
}
// "#:" lines in senseGroups' own order, then the quotations in the order they were added --
// the same order the wizard writes them in.
function extras() {
var out = [];
SENSE_GROUPS.forEach(function (group) {
var entry = lists[group.key];
var words = entry ? entry.values() : [];
if (words.length) {
out.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
return out.concat(quotes);
}
function collected() {
var held = {};
Object.keys(lists).forEach(function (key) {
held[key] = lists[key].values();
});
var leadingHeld = {};
Object.keys(leading).forEach(function (key) {
leadingHeld[key] = leading[key].slice();
});
return { text: $.trim($text.val()), lists: held, leading: leadingHeld,
quotes: quotes.slice() };
}
// One box for a leading call: etîket and kategorî take a list, maneid a single value.
function addLeadingField(def, values) {
if (leading[def.tpl]) {
return;
}
leading[def.tpl] = (values || []).slice();
var $wrap = $("<div class='dsb-adv-group'></div>")
.append($("<div class='dsb-adv-label'></div>").text(def.label));
if (def.single) {
var $input = $("<input type='text' class='dsb-input'>")
.attr("placeholder", def.label + "…").val((values || [])[0] || "");
$input.on("input", function () {
var v = $.trim($input.val());
leading[def.tpl] = v ? [v] : [];
});
$wrap.append($input);
} else {
var field = buildWordListField(def.label + "…");
if (values && values.length) {
field.setValues(values);
}
// Read back on save, like the word lists: the field owns its own rows.
leading[def.tpl] = values ? values.slice() : [];
field.$element.on("input change", function () {
leading[def.tpl] = field.values();
});
$wrap.append(field.$element);
}
$lists.append($wrap);
}
function addList(group, words) {
if (lists[group.key]) {
lists[group.key].$element.find("input").first().trigger("focus");
return;
}
var field = buildWordListField(group.label + "…");
if (words && words.length) {
field.setValues(words);
}
lists[group.key] = field;
$lists.append($("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text(group.label), field.$element));
}
// The form is opened on this sense, since a row that is not on the page has nothing for
// add-examples to attach to; only the line it builds is kept, never its page transform.
function addQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
quotes.push(job.line.replace(/^#+/, ""));
drawQuote(job.line);
job.done();
});
var drive = wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
return;
}
// It attaches itself inside the sense it was opened on, which is not where this row is:
// its title sits in that sense's example list, the form panel after it. Both move.
var $title = $li.find(".add-example-form-title").first().closest("li");
var $form = $li.find(".add-example-form").first();
if ($title.length) {
$quotes.after($title);
}
if ($form.length) {
($title.length ? $title : $quotes).after($form);
}
}
// The same shape as a saved sense's row: the one action that is used every time sits in the
// open, the word lists behind "Hevmane û Bêhtir". There is no "Maneyê biguhêre" here -- the
// box above it is the gloss.
var $rowMenu = $("<div class='dsb-more-menu'></div>").hide();
var $rowArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $rowToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($rowArrow);
$rowToggle.on("click", function (e) {
e.preventDefault();
$rowMenu.slideToggle(function () {
$rowArrow.text($rowMenu.is(":visible") ? "▾" : "▸");
});
});
function menuItem(label, open) {
return $("<a href='#' class='dsb-quick-link'></a>").text("+ " + label)
.on("click", function (e) {
e.preventDefault();
$rowMenu.slideUp(function () {
$rowArrow.text("▸");
});
open();
});
}
// The same three groups the sense menu has: what labels the sense, then its word lists.
NEW_SENSE_LEADING.forEach(function (def) {
$rowMenu.append(menuItem(def.label, function () {
addLeadingField(def);
}));
});
// senseGroups' own order, the order the "#:" lines are written in, as in the sense menu.
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).forEach(function (group) {
$rowMenu.append(menuItem(group.label, function () {
addList(group);
}));
});
$picker.append($("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin")
.on("click", function (e) {
e.preventDefault();
addQuotation();
}), $rowToggle, $rowMenu);
NEW_SENSE_LEADING.forEach(function (def) {
var values = (state.leading || {})[def.tpl];
if (values && values.length) {
addLeadingField(def, values);
}
});
SENSE_GROUPS.forEach(function (group) {
var words = state.lists[group.key];
if (words && words.length) {
addList(group, words);
}
});
// Closing is not deleting: a sense already queued keeps its entry and its row, and
// "Maneyê rake" below is what takes it out of the queue.
$cancel.on("click", function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
if (queuedNewSense()) {
redrawPreviews();
}
hideQueueIfIdle();
});
var $drop = $("<a href='#' class='dsb-quick-link dsb-field-delete'></a>")
.text("Maneyê rake")
.on("click", function (e) {
e.preventDefault();
dropQueuedNewSense();
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
});
$add.on("click", function () {
var text = definitionLine();
if (!$.trim($text.val())) {
$status.addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var lines = extras();
var held = collected();
var wikitext = ["# " + text].concat(lines.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType,
sectionOrdinal, definitionLevel, text, lines), warnings: [] };
},
$save,
$status,
// Drawn fresh every time: redrawPreviews() calls this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
// The pencil a sense carries, in the same place: at the start of the row, not
// after its content, where a block of rendered wikitext pushes it onto a line of
// its own.
var $edit = $("<span class='dsb-toolbar'></span>").append(
$("<a href='#' class='dsb-toggle'></a>")
.attr("title", GLOSS_LABEL)
.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
openNewSenseRow(held);
}));
renderWikitext(wikitext).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
// parsedSenseItem returns {html, attrs}, the sense's own markup without the
// <li> around it -- this row is that <li>.
// Inside the rendered block, ahead of its first line: outside it the pencil
// sits above the sense, since the block is block-level.
$pending.empty().append($("<div class='mw-parser-output'></div>")
.html(parsedSenseItem(html, 1).html).prepend($edit));
announceContent($pending);
}, function () {
$pending.empty().append($edit, $("<span></span>").text(text));
});
return function () {
$pending.remove();
};
},
{ key: newSenseKey, sensePath: definitionLevel, senseLanguage: language }
);
});
$box.append($text, $lists, $quotes, $picker,
$("<div class='dsb-form-actions'></div>")
.append($add, $cancel, $autoLink, $status, $drop));
$row.append($box);
$li.after($row);
$text.trigger("focus");
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: GLOSS_LABEL, build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
drk5em536a1w54cf8eylookhc5e7kvg
6535018
6535017
2026-09-20T20:25:29Z
Wikihez
7344
DefSideBoxes: the pending sense's pencil is the inline one, at the end of its line
6535018
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var GLOSS_LABEL = "Maneyê biguhêre";
// The leading calls a new sense can carry, with the template each one writes.
var NEW_SENSE_LEADING = [
{ tpl: "e", label: "Etîket" },
{ tpl: "K", label: "Kategorî" },
{ tpl: "maneid", label: "Maneid", single: true }
];
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense) {
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
return (v && v !== originalGloss) ? v : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
var newSenseKey = "newSense:" + definitionLevel.join(".");
function queuedNewSense() {
var found = null;
dsbQueue.entries.forEach(function (entry) {
if (entry.key === newSenseKey) {
found = entry;
}
});
return found;
}
// "Maneyê rake" on the row: the entry goes, and with it the rendered sense.
function dropQueuedNewSense() {
dsbQueue.entries.slice().forEach(function (entry) {
if (entry.key === newSenseKey) {
dropQueueEntry(entry);
}
});
}
// `state` reopens a sense already queued: {text, lists, quotes} as the box last held them.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
if (state) {
// The rendered row belongs to the queue entry; hide it while the box is open, but leave
// the entry alone so cancelling can put the row back.
$li.nextAll("li.dsb-new-sense-pending").first().remove();
}
openQueueBox();
state = state || { text: "", lists: {}, quotes: [] };
var lists = {};
var leading = {}; // tplKey -> [values], written in front of the gloss
var quotes = state.quotes.slice();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $box = $("<div class='dsb-new-sense'></div>");
var $text = $("<textarea class='dsb-input' rows='2'></textarea>")
.attr("placeholder", "Maneya nû binivîse…").val(state.text);
var $lists = $("<div class='dsb-new-sense-lists'></div>");
var $quotes = $("<div class='dsb-new-sense-quotes'></div>");
var $picker = $("<div class='dsb-new-sense-picker'></div>");
var $add = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
// The same button the gloss box has: a definition typed here wants linking just as much.
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>");
$autoLink.on("click", function () {
var text = $.trim($text.val());
if (!text) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(text, "", function (linked, _izah, error) {
$text.val(linked);
$autoLink.prop("disabled", false);
$status.text(error || "");
if (error) {
$status.addClass("dsb-status-error");
}
});
});
$quickRow.data("dsbNewSenseRow", $row);
function drawQuote(line) {
$quotes.append($("<div class='dsb-new-sense-quote'></div>").text(line));
}
quotes.forEach(function (extra) {
drawQuote("#" + extra);
});
// {{maneid|…}} {{e|…}} {{K|…}}, in LEADING_TPL_ORDER, ahead of the definition itself -- the
// same place insertLeadingSenseTpl puts them on a sense that already exists.
function leadingCalls() {
return LEADING_TPL_ORDER.map(function (key) {
var values = leading[key] || [];
return values.length ? "{{" + key + "|" + language + "|" + values.join("|") + "}}" : null;
}).filter(Boolean).join(" ");
}
function definitionLine() {
var calls = leadingCalls();
return (calls ? calls + " " : "") + $.trim($text.val());
}
// "#:" lines in senseGroups' own order, then the quotations in the order they were added --
// the same order the wizard writes them in.
function extras() {
var out = [];
SENSE_GROUPS.forEach(function (group) {
var entry = lists[group.key];
var words = entry ? entry.values() : [];
if (words.length) {
out.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
return out.concat(quotes);
}
function collected() {
var held = {};
Object.keys(lists).forEach(function (key) {
held[key] = lists[key].values();
});
var leadingHeld = {};
Object.keys(leading).forEach(function (key) {
leadingHeld[key] = leading[key].slice();
});
return { text: $.trim($text.val()), lists: held, leading: leadingHeld,
quotes: quotes.slice() };
}
// One box for a leading call: etîket and kategorî take a list, maneid a single value.
function addLeadingField(def, values) {
if (leading[def.tpl]) {
return;
}
leading[def.tpl] = (values || []).slice();
var $wrap = $("<div class='dsb-adv-group'></div>")
.append($("<div class='dsb-adv-label'></div>").text(def.label));
if (def.single) {
var $input = $("<input type='text' class='dsb-input'>")
.attr("placeholder", def.label + "…").val((values || [])[0] || "");
$input.on("input", function () {
var v = $.trim($input.val());
leading[def.tpl] = v ? [v] : [];
});
$wrap.append($input);
} else {
var field = buildWordListField(def.label + "…");
if (values && values.length) {
field.setValues(values);
}
// Read back on save, like the word lists: the field owns its own rows.
leading[def.tpl] = values ? values.slice() : [];
field.$element.on("input change", function () {
leading[def.tpl] = field.values();
});
$wrap.append(field.$element);
}
$lists.append($wrap);
}
function addList(group, words) {
if (lists[group.key]) {
lists[group.key].$element.find("input").first().trigger("focus");
return;
}
var field = buildWordListField(group.label + "…");
if (words && words.length) {
field.setValues(words);
}
lists[group.key] = field;
$lists.append($("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text(group.label), field.$element));
}
// The form is opened on this sense, since a row that is not on the page has nothing for
// add-examples to attach to; only the line it builds is kept, never its page transform.
function addQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
quotes.push(job.line.replace(/^#+/, ""));
drawQuote(job.line);
job.done();
});
var drive = wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
return;
}
// It attaches itself inside the sense it was opened on, which is not where this row is:
// its title sits in that sense's example list, the form panel after it. Both move.
var $title = $li.find(".add-example-form-title").first().closest("li");
var $form = $li.find(".add-example-form").first();
if ($title.length) {
$quotes.after($title);
}
if ($form.length) {
($title.length ? $title : $quotes).after($form);
}
}
// The same shape as a saved sense's row: the one action that is used every time sits in the
// open, the word lists behind "Hevmane û Bêhtir". There is no "Maneyê biguhêre" here -- the
// box above it is the gloss.
var $rowMenu = $("<div class='dsb-more-menu'></div>").hide();
var $rowArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $rowToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($rowArrow);
$rowToggle.on("click", function (e) {
e.preventDefault();
$rowMenu.slideToggle(function () {
$rowArrow.text($rowMenu.is(":visible") ? "▾" : "▸");
});
});
function menuItem(label, open) {
return $("<a href='#' class='dsb-quick-link'></a>").text("+ " + label)
.on("click", function (e) {
e.preventDefault();
$rowMenu.slideUp(function () {
$rowArrow.text("▸");
});
open();
});
}
// The same three groups the sense menu has: what labels the sense, then its word lists.
NEW_SENSE_LEADING.forEach(function (def) {
$rowMenu.append(menuItem(def.label, function () {
addLeadingField(def);
}));
});
// senseGroups' own order, the order the "#:" lines are written in, as in the sense menu.
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).forEach(function (group) {
$rowMenu.append(menuItem(group.label, function () {
addList(group);
}));
});
$picker.append($("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin")
.on("click", function (e) {
e.preventDefault();
addQuotation();
}), $rowToggle, $rowMenu);
NEW_SENSE_LEADING.forEach(function (def) {
var values = (state.leading || {})[def.tpl];
if (values && values.length) {
addLeadingField(def, values);
}
});
SENSE_GROUPS.forEach(function (group) {
var words = state.lists[group.key];
if (words && words.length) {
addList(group, words);
}
});
// Closing is not deleting: a sense already queued keeps its entry and its row, and
// "Maneyê rake" below is what takes it out of the queue.
$cancel.on("click", function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
if (queuedNewSense()) {
redrawPreviews();
}
hideQueueIfIdle();
});
var $drop = $("<a href='#' class='dsb-quick-link dsb-field-delete'></a>")
.text("Maneyê rake")
.on("click", function (e) {
e.preventDefault();
dropQueuedNewSense();
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
});
$add.on("click", function () {
var text = definitionLine();
if (!$.trim($text.val())) {
$status.addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var lines = extras();
var held = collected();
var wikitext = ["# " + text].concat(lines.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType,
sectionOrdinal, definitionLevel, text, lines), warnings: [] };
},
$save,
$status,
// Drawn fresh every time: redrawPreviews() calls this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
// The inline pencil, not the sense toolbar's: that one is positioned absolutely
// against li.dsb-def, which this row is not, and it lands off-screen.
var $edit = $("<span class='dsb-example-toolbar'></span>").append(
$("<a href='#' class='dsb-toggle'></a>")
.attr("title", GLOSS_LABEL)
.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
openNewSenseRow(held);
}));
renderWikitext(wikitext).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
// parsedSenseItem returns {html, attrs}, the sense's own markup without the
// <li> around it -- this row is that <li>.
// At the end of the definition's own line -- before the examples and word
// lists hanging under it, which are blocks and would push it down a row.
var $content = $("<div class='mw-parser-output'></div>")
.html(parsedSenseItem(html, 1).html);
var $block = $content.children("dl, ul, ol").first();
if ($block.length) {
$block.before($edit);
} else {
$content.append($edit);
}
$pending.empty().append($content);
announceContent($pending);
}, function () {
$pending.empty().append($edit, $("<span></span>").text(text));
});
return function () {
$pending.remove();
};
},
{ key: newSenseKey, sensePath: definitionLevel, senseLanguage: language }
);
});
$box.append($text, $lists, $quotes, $picker,
$("<div class='dsb-form-actions'></div>")
.append($add, $cancel, $autoLink, $status, $drop));
$row.append($box);
$li.after($row);
$text.trigger("focus");
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: GLOSS_LABEL, build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
5v9bl8izahxr44krslun4iapqnf6o2u
6535020
6535018
2026-09-20T20:34:16Z
Wikihez
7344
DefSideBoxes: a new sense is built with the sense panel itself
6535020
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var GLOSS_LABEL = "Maneyê biguhêre";
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
// pending, when given, means this panel composes a sense that is not on the page yet: it edits no
// line, and its save queues one insertion carrying the definition, its word lists and its
// quotations. Everything the user sees is the panel's own, so a new sense is edited exactly like a
// saved one. See openNewSenseRow().
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense, pending) {
// "#:" lines for a pending sense's quotations, in the order add-examples handed them over.
var pendingQuotes = pending ? pending.state.quotes.slice() : [];
var $pendingQuotes = $("<div class='dsb-new-sense-quotes'></div>");
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
if (pending) {
collectQuotation();
$moreMenu.slideUp();
return;
}
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// The form has to be opened on a sense that exists, so it opens on the one this row hangs off;
// only the line it builds is kept, and the form itself is moved under the row, title and all.
function collectQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
pendingQuotes.push(job.line.replace(/^#+/, ""));
$pendingQuotes.append($("<div class='dsb-new-sense-quote'></div>").text(job.line));
job.done();
});
if (!(wikt.addExamples.openFor && wikt.addExamples.openFor($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
return;
}
// Its title is a list item in the sense's own example list; taken out of that list so it
// does not read as a numbered sense of its own.
var $title = $li.find(".add-example-form-title").first().closest("li");
var $form = $li.find(".add-example-form").first();
if ($title.length) {
$pendingQuotes.after($("<div class='dsb-new-sense-form-title'></div>")
.append($title.contents()));
$title.remove();
}
if ($form.length) {
$quickRow.find(".dsb-new-sense-form-title").first().after($form);
}
}
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
if (pending) {
original = { gloss: pending.state.text || "", maneid: pending.state.maneid || "",
etiket: pending.state.etiket || [], kategori: pending.state.kategori || [],
words: pending.state.lists || {} };
sensePromise = $.Deferred().resolve(original).promise();
} else {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
// A pending sense has no line on the page to differ from: whatever is in the box is the sense.
return (pending || (v && v !== originalGloss)) ? (v || null) : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
var newSenseKey = "newSense:" + definitionLevel.join(".");
function queuedNewSense() {
var found = null;
dsbQueue.entries.forEach(function (entry) {
if (entry.key === newSenseKey) {
found = entry;
}
});
return found;
}
function dropQueuedNewSense() {
dsbQueue.entries.slice().forEach(function (entry) {
if (entry.key === newSenseKey) {
dropQueueEntry(entry);
}
});
}
// A sense that is not on the page yet is edited with this same panel, in a row of its own: same
// picker, same dropdown, same buttons. Only the save differs -- it writes an insertion rather
// than editing a line -- and the row renders what the insertion will say.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
if (state) {
// The rendered row belongs to the queue entry: hide it while the box is open, and leave
// the entry alone so cancelling can put it back.
$li.nextAll("li.dsb-new-sense-pending").first().remove();
}
openQueueBox();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $panel = $("<div class='dsb-quick-row dsb-new-sense'></div>");
$row.append($panel);
$li.after($row);
$quickRow.data("dsbNewSenseRow", $row);
buildAdvancedPanel($panel, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel,
isLastSense, {
state: state || { text: "", lists: {}, quotes: [], maneid: "", etiket: [], kategori: [] },
key: newSenseKey,
$row: $row,
close: function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
},
reopen: openNewSenseRow,
queued: queuedNewSense,
drop: dropQueuedNewSense
});
$panel.show();
$panel.find("textarea").first().trigger("focus");
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: GLOSS_LABEL, build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
if (pending) {
// Nothing to remove from the page: the sense only exists in the queue.
pending.drop();
pending.close();
return;
}
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary || (pending && def.key === "newSense")) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !(pending && def.key === "newSense")) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
if (pending) {
// The gloss is the sense: its box is open from the start, and the quotations it collects sit
// above the actions.
$quickRow.append($pendingQuotes);
pending.state.quotes.forEach(function (extra) {
$pendingQuotes.append($("<div class='dsb-new-sense-quote'></div>").text("#" + extra));
});
$picker.find(".dsb-field-pick").filter(function () {
return $(this).text() === GLOSS_LABEL;
}).trigger("click");
}
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// One insertion carrying the whole sense: its leading calls and definition on the "#" line, then
// a "#:" line per word list and per quotation, in the order the wizard writes them.
function newSenseWikitext(changes) {
var leading = LEADING_TPL_ORDER.map(function (key) {
var values = key === "maneid" ? (changes.maneid ? [changes.maneid] : [])
: key === "e" ? changes.etiket
: key === "K" ? changes.kategori : [];
return values && values.length
? "{{" + key + "|" + language + "|" + values.join("|") + "}}" : null;
}).filter(Boolean).join(" ");
var lines = [];
SENSE_GROUPS.forEach(function (group) {
var words = changes.words[group.key] || [];
if (words.length) {
lines.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
lines = lines.concat(pendingQuotes);
return { text: (leading ? leading + " " : "") + $.trim(changes.gloss || ""), extras: lines };
}
function pendingState(changes) {
return { text: $.trim(changes.gloss || ""), lists: changes.words, quotes: pendingQuotes.slice(),
maneid: changes.maneid, etiket: changes.etiket, kategori: changes.kategori };
}
function queueNewSense(changes) {
if (!$.trim(changes.gloss || "")) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var built = newSenseWikitext(changes);
var held = pendingState(changes);
var block = ["# " + built.text].concat(built.extras.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
pending.close();
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType, sectionOrdinal,
definitionLevel, built.text, built.extras), warnings: [] };
},
$save,
$status,
// Drawn fresh on every call: redrawPreviews() runs this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
var $edit = $("<span class='dsb-example-toolbar'></span>").append(
$("<a href='#' class='dsb-toggle'></a>").attr("title", GLOSS_LABEL)
.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
pending.reopen(held);
}));
renderWikitext(block).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
var $content = $("<div class='mw-parser-output'></div>")
.html(parsedSenseItem(html, 1).html);
var $block = $content.children("dl, ul, ol").first();
if ($block.length) {
$block.before($edit);
} else {
$content.append($edit);
}
$pending.empty().append($content);
announceContent($pending);
}, function () {
$pending.empty().append($("<span></span>").text(built.text), $edit);
});
return function () {
$pending.remove();
};
},
{ key: pending.key, sensePath: definitionLevel, senseLanguage: language }
);
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
if (pending) {
pending.close();
if (pending.queued()) {
redrawPreviews();
}
return;
}
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
if (pending) {
queueNewSense(changes);
return;
}
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
iwauw2uimx4x7cv17k450zh6tvvskiz
6535022
6535020
2026-09-20T20:35:44Z
Wikihez
7344
DefSideBoxes: open the pending gloss box once the panel is assembled
6535022
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var GLOSS_LABEL = "Maneyê biguhêre";
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
// pending, when given, means this panel composes a sense that is not on the page yet: it edits no
// line, and its save queues one insertion carrying the definition, its word lists and its
// quotations. Everything the user sees is the panel's own, so a new sense is edited exactly like a
// saved one. See openNewSenseRow().
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense, pending) {
// "#:" lines for a pending sense's quotations, in the order add-examples handed them over.
var pendingQuotes = pending ? pending.state.quotes.slice() : [];
var $pendingQuotes = $("<div class='dsb-new-sense-quotes'></div>");
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
if (pending) {
collectQuotation();
$moreMenu.slideUp();
return;
}
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// The form has to be opened on a sense that exists, so it opens on the one this row hangs off;
// only the line it builds is kept, and the form itself is moved under the row, title and all.
function collectQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
pendingQuotes.push(job.line.replace(/^#+/, ""));
$pendingQuotes.append($("<div class='dsb-new-sense-quote'></div>").text(job.line));
job.done();
});
if (!(wikt.addExamples.openFor && wikt.addExamples.openFor($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
return;
}
// Its title is a list item in the sense's own example list; taken out of that list so it
// does not read as a numbered sense of its own.
var $title = $li.find(".add-example-form-title").first().closest("li");
var $form = $li.find(".add-example-form").first();
if ($title.length) {
$pendingQuotes.after($("<div class='dsb-new-sense-form-title'></div>")
.append($title.contents()));
$title.remove();
}
if ($form.length) {
$quickRow.find(".dsb-new-sense-form-title").first().after($form);
}
}
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
if (pending) {
original = { gloss: pending.state.text || "", maneid: pending.state.maneid || "",
etiket: pending.state.etiket || [], kategori: pending.state.kategori || [],
words: pending.state.lists || {} };
sensePromise = $.Deferred().resolve(original).promise();
} else {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
// A pending sense has no line on the page to differ from: whatever is in the box is the sense.
return (pending || (v && v !== originalGloss)) ? (v || null) : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
var newSenseKey = "newSense:" + definitionLevel.join(".");
function queuedNewSense() {
var found = null;
dsbQueue.entries.forEach(function (entry) {
if (entry.key === newSenseKey) {
found = entry;
}
});
return found;
}
function dropQueuedNewSense() {
dsbQueue.entries.slice().forEach(function (entry) {
if (entry.key === newSenseKey) {
dropQueueEntry(entry);
}
});
}
// A sense that is not on the page yet is edited with this same panel, in a row of its own: same
// picker, same dropdown, same buttons. Only the save differs -- it writes an insertion rather
// than editing a line -- and the row renders what the insertion will say.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
if (state) {
// The rendered row belongs to the queue entry: hide it while the box is open, and leave
// the entry alone so cancelling can put it back.
$li.nextAll("li.dsb-new-sense-pending").first().remove();
}
openQueueBox();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $panel = $("<div class='dsb-quick-row dsb-new-sense'></div>");
$row.append($panel);
$li.after($row);
$quickRow.data("dsbNewSenseRow", $row);
buildAdvancedPanel($panel, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel,
isLastSense, {
state: state || { text: "", lists: {}, quotes: [], maneid: "", etiket: [], kategori: [] },
key: newSenseKey,
$row: $row,
close: function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
},
reopen: openNewSenseRow,
queued: queuedNewSense,
drop: dropQueuedNewSense
});
$panel.show();
$panel.find("textarea").first().trigger("focus");
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: GLOSS_LABEL, build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
if (pending) {
// Nothing to remove from the page: the sense only exists in the queue.
pending.drop();
pending.close();
return;
}
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary || (pending && def.key === "newSense")) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !(pending && def.key === "newSense")) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key]) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// One insertion carrying the whole sense: its leading calls and definition on the "#" line, then
// a "#:" line per word list and per quotation, in the order the wizard writes them.
function newSenseWikitext(changes) {
var leading = LEADING_TPL_ORDER.map(function (key) {
var values = key === "maneid" ? (changes.maneid ? [changes.maneid] : [])
: key === "e" ? changes.etiket
: key === "K" ? changes.kategori : [];
return values && values.length
? "{{" + key + "|" + language + "|" + values.join("|") + "}}" : null;
}).filter(Boolean).join(" ");
var lines = [];
SENSE_GROUPS.forEach(function (group) {
var words = changes.words[group.key] || [];
if (words.length) {
lines.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
lines = lines.concat(pendingQuotes);
return { text: (leading ? leading + " " : "") + $.trim(changes.gloss || ""), extras: lines };
}
function pendingState(changes) {
return { text: $.trim(changes.gloss || ""), lists: changes.words, quotes: pendingQuotes.slice(),
maneid: changes.maneid, etiket: changes.etiket, kategori: changes.kategori };
}
function queueNewSense(changes) {
if (!$.trim(changes.gloss || "")) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var built = newSenseWikitext(changes);
var held = pendingState(changes);
var block = ["# " + built.text].concat(built.extras.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
pending.close();
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType, sectionOrdinal,
definitionLevel, built.text, built.extras), warnings: [] };
},
$save,
$status,
// Drawn fresh on every call: redrawPreviews() runs this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
var $edit = $("<span class='dsb-example-toolbar'></span>").append(
$("<a href='#' class='dsb-toggle'></a>").attr("title", GLOSS_LABEL)
.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
pending.reopen(held);
}));
renderWikitext(block).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
var $content = $("<div class='mw-parser-output'></div>")
.html(parsedSenseItem(html, 1).html);
var $block = $content.children("dl, ul, ol").first();
if ($block.length) {
$block.before($edit);
} else {
$content.append($edit);
}
$pending.empty().append($content);
announceContent($pending);
}, function () {
$pending.empty().append($("<span></span>").text(built.text), $edit);
});
return function () {
$pending.remove();
};
},
{ key: pending.key, sensePath: definitionLevel, senseLanguage: language }
);
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
if (pending) {
pending.close();
if (pending.queued()) {
redrawPreviews();
}
return;
}
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
if (pending) {
queueNewSense(changes);
return;
}
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
if (pending) {
// The gloss box is what a new sense is: it opens with the row, and the quotations the row
// collects sit above the actions. Done here, once $actions exists.
$quickRow.append($pendingQuotes);
pending.state.quotes.forEach(function (extra) {
$pendingQuotes.append($("<div class='dsb-new-sense-quote'></div>").text("#" + extra));
});
$picker.children(".dsb-field-pick").filter(function () {
return $(this).text() === GLOSS_LABEL;
}).trigger("click");
}
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
ct1cgby6jd8gwbl5pamkcz0dd02hks6
6535023
6535022
2026-09-20T20:37:33Z
Wikihez
7344
DefSideBoxes: no nested new-sense link inside a new sense
6535023
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var GLOSS_LABEL = "Maneyê biguhêre";
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from [[Şablon:werger-ser]]: 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, firehî = column
// width (default 26em), bgcolor, valign (default top), align (default left).
var WERGER_NAMED = ["id", "stûn", "firehî", "bgcolor", "valign", "align"];
function buildWergerBlock(params) {
var parts = ["werger-ser"];
// Positional slots: 2 can only be reached past 1, so an empty 1 still has to be written.
if (params["1"] || params["2"]) {
parts.push(params["1"] || "");
}
if (params["2"]) {
parts.push(params["2"]);
}
WERGER_NAMED.forEach(function (key) {
if (params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}\n{{werger-bin}}";
}
// The opening call of the first block in `text`, as the same {1, 2, id, ...} object. Empty object when
// there is none, so a half-typed draft restores as an empty form rather than throwing.
function parseWergerBlock(text) {
var out = {};
var body = wergerSerBody(String(text || ""));
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var eq = arg.indexOf("=");
var name = eq === -1 ? "" : $.trim(arg.slice(0, eq));
if (eq !== -1 && WERGER_NAMED.indexOf(name) !== -1) {
out[name] = $.trim(arg.slice(eq + 1));
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// Everything after "{{werger-ser|" up to the "}}" that closes it, or "" when there is no call or it
// takes no parameters. Scans for the matching close rather than the first one: a gloss can hold a
// template of its own, and a lazy regex would stop inside it.
function wergerSerBody(text) {
var m = /\{\{\s*werger-ser\s*(?=[|}])/.exec(text);
if (!m) {
return "";
}
var depth = 1;
var start = m.index + m[0].length;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
return text.charAt(start) === "|" ? text.slice(start + 1, i - 1) : "";
}
}
}
return "";
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runLast ? last : normal).push(entry);
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
function queuedContent(pageContent) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast;
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// Group 1 is the spelling the page uses, group 2 the parameters: an existing {{hevwate}} stays
// {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var tplMatch = tplLineRegex.exec(lines[i]);
if (tplMatch) {
var foundKey = tplMatch[1];
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var params = tplMatch[2].split("|"); // [lang, word1, word2, ...]
var existingWords = params.slice(1);
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|([^}]*)\\}\\}\\s*$");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var m = groupLineRegex.exec(lines[i]);
if (m) {
words[canonicalTemplateKey(m[1])] = m[2].split("|").slice(1);
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
// pending, when given, means this panel composes a sense that is not on the page yet: it edits no
// line, and its save queues one insertion carrying the definition, its word lists and its
// quotations. Everything the user sees is the panel's own, so a new sense is edited exactly like a
// saved one. See openNewSenseRow().
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense, pending) {
// "#:" lines for a pending sense's quotations, in the order add-examples handed them over.
var pendingQuotes = pending ? pending.state.quotes.slice() : [];
var $pendingQuotes = $("<div class='dsb-new-sense-quotes'></div>");
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
if (pending) {
collectQuotation();
$moreMenu.slideUp();
return;
}
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// The form has to be opened on a sense that exists, so it opens on the one this row hangs off;
// only the line it builds is kept, and the form itself is moved under the row, title and all.
function collectQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
pendingQuotes.push(job.line.replace(/^#+/, ""));
$pendingQuotes.append($("<div class='dsb-new-sense-quote'></div>").text(job.line));
job.done();
});
if (!(wikt.addExamples.openFor && wikt.addExamples.openFor($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
return;
}
// Its title is a list item in the sense's own example list; taken out of that list so it
// does not read as a numbered sense of its own.
var $title = $li.find(".add-example-form-title").first().closest("li");
var $form = $li.find(".add-example-form").first();
if ($title.length) {
$pendingQuotes.after($("<div class='dsb-new-sense-form-title'></div>")
.append($title.contents()));
$title.remove();
}
if ($form.length) {
$quickRow.find(".dsb-new-sense-form-title").first().after($form);
}
}
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
if (pending) {
original = { gloss: pending.state.text || "", maneid: pending.state.maneid || "",
etiket: pending.state.etiket || [], kategori: pending.state.kategori || [],
words: pending.state.lists || {} };
sensePromise = $.Deferred().resolve(original).promise();
} else {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
}
return sensePromise;
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
fetchTemplateData(target.name).then(function (td) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
// A pending sense has no line on the page to differ from: whatever is in the box is the sense.
return (pending || (v && v !== originalGloss)) ? (v || null) : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
var newSenseKey = "newSense:" + definitionLevel.join(".");
function queuedNewSense() {
var found = null;
dsbQueue.entries.forEach(function (entry) {
if (entry.key === newSenseKey) {
found = entry;
}
});
return found;
}
function dropQueuedNewSense() {
dsbQueue.entries.slice().forEach(function (entry) {
if (entry.key === newSenseKey) {
dropQueueEntry(entry);
}
});
}
// A sense that is not on the page yet is edited with this same panel, in a row of its own: same
// picker, same dropdown, same buttons. Only the save differs -- it writes an insertion rather
// than editing a line -- and the row renders what the insertion will say.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
if (state) {
// The rendered row belongs to the queue entry: hide it while the box is open, and leave
// the entry alone so cancelling can put it back.
$li.nextAll("li.dsb-new-sense-pending").first().remove();
}
openQueueBox();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $panel = $("<div class='dsb-quick-row dsb-new-sense'></div>");
$row.append($panel);
$li.after($row);
$quickRow.data("dsbNewSenseRow", $row);
buildAdvancedPanel($panel, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel,
isLastSense, {
state: state || { text: "", lists: {}, quotes: [], maneid: "", etiket: [], kategori: [] },
key: newSenseKey,
$row: $row,
close: function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
},
reopen: openNewSenseRow,
queued: queuedNewSense,
drop: dropQueuedNewSense
});
$panel.show();
$panel.find("textarea").first().trigger("focus");
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Beşek lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: GLOSS_LABEL, build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
if (pending) {
// Nothing to remove from the page: the sense only exists in the queue.
pending.drop();
pending.close();
return;
}
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary || (pending && def.key === "newSense")) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !(pending && def.key === "newSense")) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key] && !(pending && def.key === "newSense")) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
var base = original || { maneid: "", etiket: [], kategori: [], words: {} };
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// One insertion carrying the whole sense: its leading calls and definition on the "#" line, then
// a "#:" line per word list and per quotation, in the order the wizard writes them.
function newSenseWikitext(changes) {
var leading = LEADING_TPL_ORDER.map(function (key) {
var values = key === "maneid" ? (changes.maneid ? [changes.maneid] : [])
: key === "e" ? changes.etiket
: key === "K" ? changes.kategori : [];
return values && values.length
? "{{" + key + "|" + language + "|" + values.join("|") + "}}" : null;
}).filter(Boolean).join(" ");
var lines = [];
SENSE_GROUPS.forEach(function (group) {
var words = changes.words[group.key] || [];
if (words.length) {
lines.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
lines = lines.concat(pendingQuotes);
return { text: (leading ? leading + " " : "") + $.trim(changes.gloss || ""), extras: lines };
}
function pendingState(changes) {
return { text: $.trim(changes.gloss || ""), lists: changes.words, quotes: pendingQuotes.slice(),
maneid: changes.maneid, etiket: changes.etiket, kategori: changes.kategori };
}
function queueNewSense(changes) {
if (!$.trim(changes.gloss || "")) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var built = newSenseWikitext(changes);
var held = pendingState(changes);
var block = ["# " + built.text].concat(built.extras.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
pending.close();
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType, sectionOrdinal,
definitionLevel, built.text, built.extras), warnings: [] };
},
$save,
$status,
// Drawn fresh on every call: redrawPreviews() runs this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
var $edit = $("<span class='dsb-example-toolbar'></span>").append(
$("<a href='#' class='dsb-toggle'></a>").attr("title", GLOSS_LABEL)
.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
pending.reopen(held);
}));
renderWikitext(block).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
var $content = $("<div class='mw-parser-output'></div>")
.html(parsedSenseItem(html, 1).html);
var $block = $content.children("dl, ul, ol").first();
if ($block.length) {
$block.before($edit);
} else {
$content.append($edit);
}
$pending.empty().append($content);
announceContent($pending);
}, function () {
$pending.empty().append($("<span></span>").text(built.text), $edit);
});
return function () {
$pending.remove();
};
},
{ key: pending.key, sensePath: definitionLevel, senseLanguage: language }
);
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
if (pending) {
pending.close();
if (pending.queued()) {
redrawPreviews();
}
return;
}
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
if (pending) {
queueNewSense(changes);
return;
}
var parts = senseChangeLabels(changes);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, changes);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
if (pending) {
// The gloss box is what a new sense is: it opens with the row, and the quotations the row
// collects sit above the actions. Done here, once $actions exists.
$quickRow.append($pendingQuotes);
pending.state.quotes.forEach(function (extra) {
$pendingQuotes.append($("<div class='dsb-new-sense-quote'></div>").text("#" + extra));
});
$picker.children(".dsb-field-pick").filter(function () {
return $(this).text() === GLOSS_LABEL;
}).trigger("click");
}
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Beşek lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again.
function buildSubsectionPreviewDom($h3, s, content, language) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded.
*/
function replaceSubsectionInPlace($h3, header, wikitext) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: the picker does not offer to build a Werger box, but one already
// on the page still opens, as the wikitext it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it: Werger waits for the
// translation tools it belongs to, and offering a box those cannot yet fill misleads.
// Werger also carries lang: "ku" -- it is a Kurdish-entry section, so it is not offered
// elsewhere either.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
$cancel.on("click", function () {
resetForm();
$panel.hide();
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return buildWergerBlock(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(parseWergerBlock(value));
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// A translation box: the {{werger-ser}}/{{werger-bin}} pair, with the sense it covers and the
// layout parameters that template accepts. The rows in between are left to the translation editor
// gadget, which is what edits them on the rendered page.
// Reads and writes through fieldText()/setFieldText() as wikitext, so drafts, save and the queue
// need no special case beyond the append below.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded, and without it the label falls back to
// sitting inline and a short one ("id") is overlapped by its own box.
function row(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
// {{werger-ser}} also takes stûn, firehî, bgcolor, valign and align, but they only restyle the
// box and its defaults are what these pages use, so the form does not offer them. They are
// still in WERGER_NAMED, so a block that already sets one is read back correctly.
var $main = $("<div class='dsb-werger-main'></div>").append(
row("1", "Mane", "ava vexwarinê"),
row("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
row("id", "id", "ne mecbûrî — IDya tercimeyê")
);
var $note = $("<p class='dsb-werger-note'></p>").text(
"Qutiyeke Wergerê ya vala lê zêde dikî. Tercimeyan bi amûra wergerê dûre binivîse."
);
wergerForm = {
read: function () {
var out = {};
$.each(inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
out[key] = value;
}
});
return out;
},
set: function (params) {
$.each(inputs, function (key, $input) {
$input.val((params && params[key]) || "");
});
}
};
$fieldWrap.empty().append($note, $main);
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
if (current.fromBullets) {
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroups()))
: subsectionBodyWikitext(language, s, readGroups());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
// Werger always adds a box, even to a section that is already there, so its button never says
// "Biguhêre".
$save.text(s.existing && s.kind !== "werger" ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if ((s.existing || s.place) && s.kind !== "werger") {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroups();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
// A Werger section holds any number of boxes, so an existing one is appended to rather than
// replaced; every other kind edits the single body it owns.
var appending = s.kind === "werger" && s.existing;
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
s.kind === "werger"
? "qutiyeke wergerê hate zêdekirin"
: "beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
if (appending) {
var current = readPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective);
var existingText = current ? $.trim(current.text || "") : "";
body = existingText ? existingText + "\n\n" + content : content;
}
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// Werger is a box the translation tools fill in afterwards, so there is nothing to draw for
// it. A new subsection gets a block of its own at the position it will land in; one already
// on the page is redrawn where it stands.
s.kind === "werger" ? null : function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Beşek lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox
};
}());
// </nowiki>
29wz6ztrf47a22927bg9w1cndgacgcd
Bikarhêner:Wikihez/Gadget-test/dsb-panels.css
2
1358910
6534962
6532616
2026-09-20T18:41:30Z
Wikihez
7344
DefSideBoxes: strike a quotation queued for removal; a failed hand-over no longer leaves the form disabled
6534962
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.css]]
*/
/* ── Searchable select ───────────────────────────────────────────────
The native <select> is hidden inside the wrapper and still holds the value; the input filters the list. */
.dsb-ac-wrap {
position: relative;
display: block;
}
.dsb-ac-input {
width: 100%;
box-sizing: border-box;
}
.dsb-ac-results {
position: absolute;
z-index: 30;
top: 100%;
left: 0;
right: 0;
max-height: 260px;
margin: 2px 0 0;
padding: 4px;
overflow-y: auto;
list-style: none;
font-family: var(--wikt-sans);
font-size: 13px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
.dsb-ac-results li {
padding: 6px 8px;
border-radius: 6px;
cursor: pointer;
}
.dsb-ac-results li:hover,
.dsb-ac-results li.dsb-ac-active {
background: var(--wikt-accent-tint);
}
/* ── Etîmolojî editing surface ───────────────────────────────────────
A contenteditable line of wikitext: text is typed straight into it, template calls sit in it as chips. */
.dsb-tpl-surface {
min-height: 72px;
margin-top: 6px;
padding: 8px 10px;
font-family: var(--wikt-sans);
font-size: 14px;
line-height: 1.6;
color: var(--wikt-ink);
white-space: pre-wrap;
word-wrap: break-word;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-tpl-surface:focus {
outline: none;
border-color: var(--wikt-accent);
box-shadow: 0 0 0 2px var(--wikt-accent-tint);
}
/* The chips: visibly not prose, and atomic to the caret. */
/* Takes the call off the line without going through the parameter dialog -- on a phone there is no
reliable backspace onto a contenteditable="false" chip. */
.dsb-tpl-chip-remove {
position: relative;
margin-left: 5px;
padding: 0 2px;
font-family: var(--wikt-sans);
font-size: 13px;
line-height: 1;
color: var(--wikt-accent-deep);
opacity: 0.6;
cursor: pointer;
}
.dsb-tpl-chip-remove:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* The glyph stays small; the tap target grows invisibly around it. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-tpl-chip-remove {
opacity: 1;
}
.dsb-tpl-chip-remove::after {
content: "";
position: absolute;
inset: -10px -8px;
}
}
.dsb-tpl-chip {
display: inline-block;
margin: 0 1px;
padding: 1px 6px;
font-family: var(--wikt-mono);
font-size: 12.5px;
color: var(--wikt-accent-deep);
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
vertical-align: baseline;
background: var(--wikt-accent-tint);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
touch-action: manipulation;
cursor: pointer;
user-select: none;
}
.dsb-tpl-chip:hover,
.dsb-tpl-chip:focus {
background: var(--wikt-field-bg);
border-color: var(--wikt-accent);
outline: none;
}
/* ── Template parameter form ─────────────────────────────────────────
.dsb-tpl-name/-desc/-param-row/-help/-undocumented/-modifier-list and the deprecated-label override now
live in cite.css (wikt.cite's own stylesheet): buildTemplateParamForm() moved there so start.js's own
Etîmolojî/Çavkanî pickers can open the same dialog. */
/* One-line variant: label beside its box rather than above it, for a single field whose name would
otherwise read as a heading for everything below it (the word groups' "Sernav"). */
.dsb-param-row-inline > .dsb-adv-label {
display: flex;
gap: 8px;
align-items: center;
}
/* Sernav is optional and names the group; the word boxes under it are what the form is actually for.
Letting it stretch to the full row made the one thing you may leave blank the widest box on the
card, so it takes only the space its own content needs. */
.dsb-param-row-inline .dsb-input {
flex: 0 1 auto;
width: 12em;
max-width: 40%;
/* .dsb-input's own 16em floor would win over the width above and make this the widest box on the
card, which is the opposite of the point. */
min-width: 0;
max-width: 100%;
min-width: 0;
}
.dsb-gloss-raw {
font-size: 12px;
}
/* Disclosure arrow on "Hevmane û Bêhtir", flipped when the menu is open. */
.dsb-more-arrow {
font-size: 10px;
vertical-align: 1px;
}
/* The POS panel's follow-up questions (cinsiyet / lektur): label above its select, like the field boxes. */
.dsb-pos-param-row {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 8px;
}
/* ── "Guhartina pêşketî" (advanced editing) panel ──────────────────────
Revealed by the pencil toggle above: every per-sense action this gadget offers, queued together with one
"Lê zêde bike". A plain inline row, not a bordered/background card. */
.dsb-quick-row {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
box-sizing: border-box;
max-width: 520px;
margin: 4px 0 0 16px;
font-family: var(--wikt-sans);
font-size: 12px;
color: var(--wikt-ink-2);
}
.dsb-quick-link {
align-self: flex-start;
margin-bottom: 10px;
font-size: 12px;
color: var(--wikt-accent);
text-decoration: none;
white-space: nowrap;
cursor: pointer;
}
/* The links under a word list ("+ Komek lê zêde bike", "Wekî wîkîtext biguhêre") sit in the field wrap
rather than in a flex column, so they need their own spacing or they run together. */
.dsb-subsection-field > .dsb-quick-link {
display: inline-block;
margin-right: 14px;
}
.dsb-quick-link:hover,
.dsb-quick-link:focus {
color: var(--wikt-accent-deep);
text-decoration: underline;
}
/* The picker row: gloss + the two primary word groups as direct "+ <field>" links, plus a "Hevmane û Bêhtir" toggle
for the rest -- clicking a link opens its box above the buttons and hides the link; .dsb-box-remove un-hides it.
position: relative anchors .dsb-more-menu's overlay below (see there). */
.dsb-field-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 4px 14px;
}
.dsb-field-picker .dsb-quick-link {
margin-bottom: 0;
}
/* Under the buttons once a box is open: a little space so the links do not read as part of the buttons. */
.dsb-open-boxes:not(:empty) ~ .dsb-field-picker {
margin-top: 8px;
}
/* The "Hevmane û Bêhtir" dropdown: every non-primary field link (plus Nimûne/jêgirtin, which sits first) stacked as a
plain vertical list. Floats over the page as an overlay (positioned against .dsb-field-picker) instead of
pushing $openBoxes/the rest of the definition down when it opens. */
.dsb-more-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 10;
display: flex;
flex-direction: column;
align-items: flex-start;
width: max-content;
max-width: min(320px, calc(100vw - 40px));
gap: 6px;
margin: 2px 0 0 4px;
padding: 8px 12px;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-field);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
}
.dsb-more-menu .dsb-quick-link {
margin-bottom: 0;
}
/* Boxes the user has actually opened, stacked in the order they were added -- several can be open at once. */
.dsb-open-boxes {
display: flex;
flex-direction: column;
}
/* Nothing open: take it out of the flow so the links do not sit above an empty gap. */
.dsb-open-boxes:empty {
display: none;
}
/* One field (its label + input) within the panel -- always a currently-open, removable box. */
.dsb-adv-group {
position: relative;
margin-bottom: 14px;
padding-right: 20px;
}
.dsb-adv-label {
margin-bottom: 4px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
/* The example sentence beside a word list's name: same text the wizard shows, so it reads as the
quieter half of the label rather than a second heading. */
.dsb-adv-hint {
margin-left: 6px;
font-weight: 400;
font-style: italic;
color: var(--wikt-muted);
}
/* The shared image rows (util.js's buildImageListField): one label + box per line, like the parameter
rows above them. Three identical boxes twice over is unreadable without a break between them, so each
image is its own block with its own name. */
.dsb-resim-list {
margin: 0;
padding-left: 0;
}
.dsb-resim-item {
list-style: none;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-resim-item:first-child {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.dsb-resim-index {
margin-bottom: 6px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
.dsb-box-remove {
position: absolute;
top: 0;
right: 0;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-box-remove:hover,
.dsb-box-remove:focus {
color: var(--wikt-accent-deep);
}
.dsb-form-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.dsb-input {
box-sizing: border-box;
min-width: 16em;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
/* The native corner is replaced by util.js's grip (see wikt-edit.css), which drags vertically only and
remembers the height. */
.dsb-subsection-panel textarea.dsb-input,
.dsb-adv-group textarea.dsb-input {
resize: none;
}
.dsb-input::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
.dsb-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* ── Plain tag field (Etîket, Kategorî) ────────────────────────────────
Plain type-and-commit tags with no suggestion panel; unlike Hevmane/Dijmane/etc., these carry no modifiers. */
.dsb-tagfield {
box-sizing: border-box;
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
min-width: 16em;
padding: 4px 6px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.dsb-tagfield:focus-within {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
}
.dsb-tag {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
padding: 3px 6px 3px 10px;
}
.dsb-tag-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
.dsb-tag-remove:hover {
color: var(--wikt-danger);
}
.dsb-tagfield .dsb-input {
flex: 1 1 8em;
min-width: 8em;
border: 0;
padding: 3px 2px;
background: transparent;
box-shadow: none;
}
.dsb-tagfield .dsb-input:focus {
box-shadow: none;
}
.dsb-save,
.dsb-cancel,
.dsb-delete,
.dsb-autolink {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
/* One filled pill per row — the same rule the form's footer follows. */
.dsb-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-cancel {
background: transparent;
border: 1px solid transparent;
color: var(--wikt-ink-3);
}
.dsb-cancel:hover {
border-color: var(--wikt-field-edge);
color: var(--wikt-ink);
}
/* Removing a heading: plain like Betal bike, in the danger colour, pushed to the far end of the row. */
.dsb-delete {
margin-left: auto;
background: transparent;
border: 1px solid transparent;
color: var(--wikt-danger);
}
.dsb-delete:hover {
border-color: var(--wikt-danger);
}
/* The heading's name, above the section's own box. */
.dsb-heading-name-wrap {
margin-bottom: 10px;
}
/* A heading queued for removal, with everything it owns, until the queue is saved or the change dropped. */
.dsb-preview-removed {
opacity: 0.5;
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
/* An outline pill, distinct from both the filled .dsb-save and the plain-text .dsb-cancel -- a secondary
utility action, not the primary save nor a dismissal. */
.dsb-autolink {
background: transparent;
border: 1px solid var(--wikt-accent-edge);
color: var(--wikt-accent);
}
.dsb-autolink:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-autolink:disabled {
opacity: 0.55;
cursor: progress;
}
/* Status line. Color comes from .dsb-status-ok/.dsb-status-error (added by the .js on success/failure)
rather than an inline style, so these tokens (and dark mode) apply. */
.dsb-status {
font-family: var(--wikt-sans);
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-status-ok { color: var(--wikt-ok); }
.dsb-status-error { color: var(--wikt-danger); }
/* ── "+ Beşek lê zêde bike" ──────────────────────────────────────────
An accent-coloured action link, like the form's .wiktku-adv-toggle:
it opens something, so it is not muted grey. */
/* iOS Safari zooms the page in whenever a field it focuses has text under 16px, and everything here is
12-13px for density. On touch the form controls go up to the size that stops it; the pointer query
leaves the desktop layout alone. */
@media (pointer: coarse) {
.dsb-input,
.dsb-tag-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
font-size: 16px;
}
/* Full width, whatever the viewport reports: a phone asked to render the desktop site says ~980px and
would otherwise leave these at their desktop size. */
.dsb-subsection-panel .dsb-input,
.dsb-subsection-panel .dsb-subsection-select,
.dsb-convert-pos-panel .dsb-convert-pos-select {
width: 100%;
max-width: 100%;
}
/* Sernav is the exception: it names the group, so it stays visibly smaller than the boxes it heads.
The cap matters more than the em width -- at 16px, 12em came out the same size as a word box. */
.dsb-subsection-panel .dsb-param-row-inline .dsb-input {
width: 8em;
max-width: 40%;
}
}
/* The image rows reuse the inline row, but nothing here is optional the way a group's "Sernav" is:
a file name and a caption want the whole row. */
.dsb-resim-list .dsb-param-row-inline > .dsb-adv-label,
.dsb-resim-list .dsb-tpl-param-row > label {
display: flex;
gap: 8px;
align-items: center;
}
.dsb-resim-list .dsb-param-row-inline .dsb-input,
.dsb-resim-list .dsb-tpl-param-row .dsb-input {
flex: 1 1 auto;
width: auto;
max-width: 100%;
}
/* ── Subsection panel ────────────────────────────────────────────────
A sunken card rather than a grey utility box; fields stack so the
picker and its input are not competing for one narrow row. */
.dsb-subsection-select,
.dsb-convert-pos-select {
box-sizing: border-box;
width: 100%;
max-width: 100%;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-subsection-select:focus,
.dsb-convert-pos-select:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The template-insert picker sits above the Çavkanî textarea, not in place of it -- a little breathing
room so the two don't read as one control. */
.dsb-cavkani-picker {
margin-bottom: 6px;
}
.dsb-subsection-field {
min-width: 0;
}
.dsb-subsection-words,
.dsb-subsection-text {
box-sizing: border-box;
width: 100%;
min-width: 0;
}
.dsb-subsection-text {
line-height: 1.6;
resize: vertical;
}
/* ── Gloss field inside the advanced-editing panel ────────────────────
Same shape as .dsb-subsection-text. */
.dsb-edit-gloss {
width: 100%;
line-height: 1.6;
resize: vertical;
}
/* ── Live "not saved yet" previews ─────────────────────────────────────
Dropped into the rendered page at the change's own spot. Dashed edge + a small badge mark them as
pending, not real content; they disappear on "Betal bike" or once the real save lands. */
.dsb-preview-block,
.dsb-preview-inline {
margin: 8px 0;
padding: 8px 12px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-accent-tint);
border: 1px dashed var(--wikt-accent-edge);
border-radius: var(--wikt-r-field);
}
.dsb-preview-inline {
margin-left: 26px;
max-width: 520px;
}
.dsb-preview-heading {
margin: 0 0 4px;
font-size: 15px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-preview-badge {
display: inline-block;
margin-bottom: 6px;
padding: 1px 8px;
font-size: 11px;
font-weight: 500;
color: var(--wikt-accent-deep);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
}
/* Parser output goes in here, so it drops the panel's own font and colour and takes the page's \u2014 the
point of the box is that what is inside it looks exactly like what will be saved. */
.dsb-preview-rendered,
.dsb-preview-rendered-inline {
font-family: inherit;
font-size: 14px;
color: var(--wikt-ink);
}
.dsb-preview-rendered-inline {
display: inline;
}
.dsb-preview-rendered-inline .mw-parser-output,
.dsb-preview-rendered-inline .mw-parser-output > p {
display: inline;
}
.dsb-preview-list,
.dsb-preview-text {
margin: 0;
padding-left: 18px;
}
.dsb-preview-text {
padding-left: 0;
}
/* ── Floating save-queue widget ─────────────────────────────────────
This gadget's own small floating box. One "Lê zêde bike" click anywhere on the page adds a line here;
"Guhertinan qeyd bike" makes the one real save; "Betal bike" drops everything queued without touching the page. */
.dsb-queue-box {
position: fixed;
top: 60px;
right: 16px;
z-index: 1000;
box-sizing: border-box;
width: 280px;
max-width: calc(100vw - 32px);
max-height: calc(100vh - 90px);
overflow-y: auto;
padding: 12px 14px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent);
border-radius: var(--wikt-r-card);
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.16);
}
/* An OOUI dialog sits low in the page's stacking order (z-index 4), so no z-index here can go under it
without dropping behind the article as well. While one is open -- on a phone it fills the screen --
the box is taken off instead; wikt.cite puts the class on while its dialogs are showing. */
body.wikt-dialog-open .dsb-queue-box {
display: none;
}
.dsb-queue-title {
margin-right: 20px;
font-size: 13px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-queue-close {
float: right;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-queue-close:hover,
.dsb-queue-close:focus {
color: var(--wikt-accent-deep);
}
/* Stands in for the list before anything is queued, saying what the save button is for. */
.dsb-queue-empty {
margin: 8px 0;
font-size: 12px;
line-height: 1.5;
color: var(--wikt-ink-3);
}
.dsb-queue-list {
margin: 8px 0;
padding-left: 18px;
max-height: 160px;
overflow-y: auto;
}
.dsb-queue-list li {
margin-bottom: 4px;
}
/* Drops one queued change. Dim until its line is hovered, so the list stays a list and not a row of
buttons. */
.dsb-queue-drop {
margin-left: 6px;
padding: 0 3px;
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
opacity: 0;
transition: opacity 0.12s ease, color 0.12s ease;
}
.dsb-queue-list li:hover .dsb-queue-drop,
.dsb-queue-drop:focus {
opacity: 1;
}
.dsb-queue-drop:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* No hover on touch, so the button is simply there. */
@media (hover: none), (pointer: coarse) {
.dsb-queue-drop {
opacity: 1;
}
}
.dsb-queue-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
}
.dsb-queue-save,
.dsb-queue-cancel {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.dsb-queue-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-queue-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-queue-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-queue-cancel {
background: transparent;
border: 1px solid var(--wikt-field-edge);
color: var(--wikt-ink-3);
}
.dsb-queue-cancel:hover {
border-color: var(--wikt-ink-3);
color: var(--wikt-ink);
}
.dsb-queue-status {
margin-top: 6px;
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-queue-status.dsb-status-ok { color: var(--wikt-ok); }
.dsb-queue-status.dsb-status-error { color: var(--wikt-danger); }
/* ── Minerva (mobile) ────────────────────────────────────────────────
Narrower viewport: keep the panel on screen and give every tap
target the form's 44px minimum. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-quick-row {
font-size: 13px;
margin-left: 20px;
}
.dsb-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
min-height: 40px;
/* >=16px stops iOS Safari zooming the page on focus. */
font-size: 16px;
}
.dsb-tagfield .dsb-input {
min-height: 32px;
}
.dsb-tag-remove {
padding: 0 6px;
}
.dsb-save,
.dsb-cancel,
.dsb-autolink,
.dsb-queue-save,
.dsb-queue-cancel {
min-height: 40px;
padding: 8px 16px;
}
.dsb-queue-box {
top: auto;
bottom: 8px;
right: 8px;
left: 8px;
width: auto;
}
}
/* ── Dark mode ───────────────────────────────────────────────────────
Same warm dark palette and same explicit-signal-only rule as the
form's stylesheet: no bare prefers-color-scheme fallback, since
legacy Vector sets no theme class and would go dark on its own. */
html.skin-theme-clientpref-night .dsb-save,
html.skin-nightmode .dsb-save,
html.skin-theme-clientpref-night .dsb-queue-save,
html.skin-nightmode .dsb-queue-save {
color: #14213a;
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .dsb-save,
html.skin-theme-clientpref-os .dsb-queue-save {
color: #14213a;
}
}
/* Rules the menu's three sections apart (see MENU_GROUPS). */
.dsb-more-menu .dsb-menu-group-start {
margin-top: 7px;
padding-top: 7px;
border-top: 1px solid var(--wikt-rule-soft);
}
/* Deleting a sense is destructive, so it reads in the danger colour -- but it shares the last section
with "Maneyeke din zêde bike" (both act on the sense itself), so it carries no rule of its own. */
.dsb-more-menu .dsb-field-delete {
color: var(--wikt-danger);
}
.dsb-more-menu .dsb-field-delete:hover,
.dsb-more-menu .dsb-field-delete:focus {
color: var(--wikt-danger);
text-decoration: underline;
}
/* A sense queued for deletion, struck through until the queue is saved or the change dropped. */
li.dsb-sense-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
li.dsb-sense-doomed .dsb-quick-row {
text-decoration: none;
}
/* A quotation or example queued for removal, marked where it sits like a doomed sense. */
.dsb-example-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
opacity: 0.75;
}
/* Queue confirmation for row actions that have no field box of their own. */
.dsb-quick-note {
display: block;
margin-top: 6px;
}
/* Dropped by anything that cannot live in a 520px column: a long etymology, and a word list wide
enough to have gone to two columns. */
.dsb-subsection-panel.dsb-panel-wide {
max-width: none;
}
/* Word-list groups: one "{{kol3}}" per block, told apart only when there is more than one. */
.dsb-wordgroups-many .dsb-wordgroup {
padding-top: 10px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-wordgroups-many .dsb-wordgroup:first-child {
padding-top: 0;
border-top: 0;
}
.dsb-group-remove {
color: var(--wikt-danger);
}
/* A sernav-headed group inside an unsaved-section preview. */
.dsb-preview-subheading {
margin-top: 6px;
font-weight: 600;
}
/* ── Werger box form ──
The {{werger-ser}} parameters. Reuses .dsb-input / .dsb-adv-label from the other panels; only the
note and the disclosure body need anything of their own. */
.dsb-werger-note {
margin: 0 0 10px;
color: var(--wikt-muted);
font-size: 12px;
line-height: 1.5;
}
.dsb-werger-main {
display: flex;
flex-direction: column;
gap: 10px;
}
/* The box is nested inside its label, so it is made a block to sit under the label text instead of
beside it -- inline, a short label like "id" ends up behind its own box. */
.dsb-werger-row .dsb-adv-label {
display: block;
margin-bottom: 0;
}
.dsb-werger-row .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* ── Îzaha maneyî ──
Opens under the Mane box; the two are one gloss, so it sits tight against it. */
.dsb-izah-wrap {
margin-top: 6px;
}
.dsb-izah-wrap .dsb-adv-label {
display: block;
}
.dsb-izah-wrap .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* A sense redrawn from the change that is queued for it, or a whole new sense waiting in the list's own
numbering. No badge and no frame: the point is that it reads the way it will read once saved. The tint
is the only sign, and the queue box is what actually says nothing is published yet. */
.dsb-sense-preview {
background: var(--wikt-accent-tint);
box-shadow: -3px 0 0 var(--wikt-accent-edge);
}
/* Kept in the tree so another gadget's form still has somewhere to open, but out of sight while the
preview above draws the same content. */
.dsb-preview-hidden {
display: none !important;
}
li.dsb-sense-preview {
display: list-item;
padding-left: 4px;
}
/* </nowiki> */
nx81rhkade810nw0v9mgjguuh4xq0oi
6534977
6534962
2026-09-20T19:00:03Z
Wikihez
7344
DefSideBoxes: compose a new sense with its word lists in one queued insertion
6534977
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.css]]
*/
/* ── Searchable select ───────────────────────────────────────────────
The native <select> is hidden inside the wrapper and still holds the value; the input filters the list. */
.dsb-ac-wrap {
position: relative;
display: block;
}
.dsb-ac-input {
width: 100%;
box-sizing: border-box;
}
.dsb-ac-results {
position: absolute;
z-index: 30;
top: 100%;
left: 0;
right: 0;
max-height: 260px;
margin: 2px 0 0;
padding: 4px;
overflow-y: auto;
list-style: none;
font-family: var(--wikt-sans);
font-size: 13px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
.dsb-ac-results li {
padding: 6px 8px;
border-radius: 6px;
cursor: pointer;
}
.dsb-ac-results li:hover,
.dsb-ac-results li.dsb-ac-active {
background: var(--wikt-accent-tint);
}
/* ── Etîmolojî editing surface ───────────────────────────────────────
A contenteditable line of wikitext: text is typed straight into it, template calls sit in it as chips. */
.dsb-tpl-surface {
min-height: 72px;
margin-top: 6px;
padding: 8px 10px;
font-family: var(--wikt-sans);
font-size: 14px;
line-height: 1.6;
color: var(--wikt-ink);
white-space: pre-wrap;
word-wrap: break-word;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-tpl-surface:focus {
outline: none;
border-color: var(--wikt-accent);
box-shadow: 0 0 0 2px var(--wikt-accent-tint);
}
/* The chips: visibly not prose, and atomic to the caret. */
/* Takes the call off the line without going through the parameter dialog -- on a phone there is no
reliable backspace onto a contenteditable="false" chip. */
.dsb-tpl-chip-remove {
position: relative;
margin-left: 5px;
padding: 0 2px;
font-family: var(--wikt-sans);
font-size: 13px;
line-height: 1;
color: var(--wikt-accent-deep);
opacity: 0.6;
cursor: pointer;
}
.dsb-tpl-chip-remove:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* The glyph stays small; the tap target grows invisibly around it. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-tpl-chip-remove {
opacity: 1;
}
.dsb-tpl-chip-remove::after {
content: "";
position: absolute;
inset: -10px -8px;
}
}
.dsb-tpl-chip {
display: inline-block;
margin: 0 1px;
padding: 1px 6px;
font-family: var(--wikt-mono);
font-size: 12.5px;
color: var(--wikt-accent-deep);
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
vertical-align: baseline;
background: var(--wikt-accent-tint);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
touch-action: manipulation;
cursor: pointer;
user-select: none;
}
.dsb-tpl-chip:hover,
.dsb-tpl-chip:focus {
background: var(--wikt-field-bg);
border-color: var(--wikt-accent);
outline: none;
}
/* ── Template parameter form ─────────────────────────────────────────
.dsb-tpl-name/-desc/-param-row/-help/-undocumented/-modifier-list and the deprecated-label override now
live in cite.css (wikt.cite's own stylesheet): buildTemplateParamForm() moved there so start.js's own
Etîmolojî/Çavkanî pickers can open the same dialog. */
/* One-line variant: label beside its box rather than above it, for a single field whose name would
otherwise read as a heading for everything below it (the word groups' "Sernav"). */
.dsb-param-row-inline > .dsb-adv-label {
display: flex;
gap: 8px;
align-items: center;
}
/* Sernav is optional and names the group; the word boxes under it are what the form is actually for.
Letting it stretch to the full row made the one thing you may leave blank the widest box on the
card, so it takes only the space its own content needs. */
.dsb-param-row-inline .dsb-input {
flex: 0 1 auto;
width: 12em;
max-width: 40%;
/* .dsb-input's own 16em floor would win over the width above and make this the widest box on the
card, which is the opposite of the point. */
min-width: 0;
max-width: 100%;
min-width: 0;
}
.dsb-gloss-raw {
font-size: 12px;
}
/* Disclosure arrow on "Hevmane û Bêhtir", flipped when the menu is open. */
.dsb-more-arrow {
font-size: 10px;
vertical-align: 1px;
}
/* The POS panel's follow-up questions (cinsiyet / lektur): label above its select, like the field boxes. */
.dsb-pos-param-row {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 8px;
}
/* ── "Guhartina pêşketî" (advanced editing) panel ──────────────────────
Revealed by the pencil toggle above: every per-sense action this gadget offers, queued together with one
"Lê zêde bike". A plain inline row, not a bordered/background card. */
.dsb-quick-row {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
box-sizing: border-box;
max-width: 520px;
margin: 4px 0 0 16px;
font-family: var(--wikt-sans);
font-size: 12px;
color: var(--wikt-ink-2);
}
.dsb-quick-link {
align-self: flex-start;
margin-bottom: 10px;
font-size: 12px;
color: var(--wikt-accent);
text-decoration: none;
white-space: nowrap;
cursor: pointer;
}
/* The links under a word list ("+ Komek lê zêde bike", "Wekî wîkîtext biguhêre") sit in the field wrap
rather than in a flex column, so they need their own spacing or they run together. */
.dsb-subsection-field > .dsb-quick-link {
display: inline-block;
margin-right: 14px;
}
.dsb-quick-link:hover,
.dsb-quick-link:focus {
color: var(--wikt-accent-deep);
text-decoration: underline;
}
/* The picker row: gloss + the two primary word groups as direct "+ <field>" links, plus a "Hevmane û Bêhtir" toggle
for the rest -- clicking a link opens its box above the buttons and hides the link; .dsb-box-remove un-hides it.
position: relative anchors .dsb-more-menu's overlay below (see there). */
.dsb-field-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 4px 14px;
}
.dsb-field-picker .dsb-quick-link {
margin-bottom: 0;
}
/* Under the buttons once a box is open: a little space so the links do not read as part of the buttons. */
.dsb-open-boxes:not(:empty) ~ .dsb-field-picker {
margin-top: 8px;
}
/* The "Hevmane û Bêhtir" dropdown: every non-primary field link (plus Nimûne/jêgirtin, which sits first) stacked as a
plain vertical list. Floats over the page as an overlay (positioned against .dsb-field-picker) instead of
pushing $openBoxes/the rest of the definition down when it opens. */
.dsb-more-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 10;
display: flex;
flex-direction: column;
align-items: flex-start;
width: max-content;
max-width: min(320px, calc(100vw - 40px));
gap: 6px;
margin: 2px 0 0 4px;
padding: 8px 12px;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-field);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
}
.dsb-more-menu .dsb-quick-link {
margin-bottom: 0;
}
/* Boxes the user has actually opened, stacked in the order they were added -- several can be open at once. */
.dsb-open-boxes {
display: flex;
flex-direction: column;
}
/* Nothing open: take it out of the flow so the links do not sit above an empty gap. */
.dsb-open-boxes:empty {
display: none;
}
/* One field (its label + input) within the panel -- always a currently-open, removable box. */
.dsb-adv-group {
position: relative;
margin-bottom: 14px;
padding-right: 20px;
}
.dsb-adv-label {
margin-bottom: 4px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
/* The example sentence beside a word list's name: same text the wizard shows, so it reads as the
quieter half of the label rather than a second heading. */
.dsb-adv-hint {
margin-left: 6px;
font-weight: 400;
font-style: italic;
color: var(--wikt-muted);
}
/* The shared image rows (util.js's buildImageListField): one label + box per line, like the parameter
rows above them. Three identical boxes twice over is unreadable without a break between them, so each
image is its own block with its own name. */
.dsb-resim-list {
margin: 0;
padding-left: 0;
}
.dsb-resim-item {
list-style: none;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-resim-item:first-child {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.dsb-resim-index {
margin-bottom: 6px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
.dsb-box-remove {
position: absolute;
top: 0;
right: 0;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-box-remove:hover,
.dsb-box-remove:focus {
color: var(--wikt-accent-deep);
}
.dsb-form-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.dsb-input {
box-sizing: border-box;
min-width: 16em;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
/* The native corner is replaced by util.js's grip (see wikt-edit.css), which drags vertically only and
remembers the height. */
.dsb-subsection-panel textarea.dsb-input,
.dsb-adv-group textarea.dsb-input {
resize: none;
}
.dsb-input::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
.dsb-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* ── Plain tag field (Etîket, Kategorî) ────────────────────────────────
Plain type-and-commit tags with no suggestion panel; unlike Hevmane/Dijmane/etc., these carry no modifiers. */
.dsb-tagfield {
box-sizing: border-box;
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
min-width: 16em;
padding: 4px 6px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.dsb-tagfield:focus-within {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
}
.dsb-tag {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
padding: 3px 6px 3px 10px;
}
.dsb-tag-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
.dsb-tag-remove:hover {
color: var(--wikt-danger);
}
.dsb-tagfield .dsb-input {
flex: 1 1 8em;
min-width: 8em;
border: 0;
padding: 3px 2px;
background: transparent;
box-shadow: none;
}
.dsb-tagfield .dsb-input:focus {
box-shadow: none;
}
.dsb-save,
.dsb-cancel,
.dsb-delete,
.dsb-autolink {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
/* One filled pill per row — the same rule the form's footer follows. */
.dsb-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-cancel {
background: transparent;
border: 1px solid transparent;
color: var(--wikt-ink-3);
}
.dsb-cancel:hover {
border-color: var(--wikt-field-edge);
color: var(--wikt-ink);
}
/* Removing a heading: plain like Betal bike, in the danger colour, pushed to the far end of the row. */
.dsb-delete {
margin-left: auto;
background: transparent;
border: 1px solid transparent;
color: var(--wikt-danger);
}
.dsb-delete:hover {
border-color: var(--wikt-danger);
}
/* The heading's name, above the section's own box. */
.dsb-heading-name-wrap {
margin-bottom: 10px;
}
/* A heading queued for removal, with everything it owns, until the queue is saved or the change dropped. */
.dsb-preview-removed {
opacity: 0.5;
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
/* An outline pill, distinct from both the filled .dsb-save and the plain-text .dsb-cancel -- a secondary
utility action, not the primary save nor a dismissal. */
.dsb-autolink {
background: transparent;
border: 1px solid var(--wikt-accent-edge);
color: var(--wikt-accent);
}
.dsb-autolink:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-autolink:disabled {
opacity: 0.55;
cursor: progress;
}
/* Status line. Color comes from .dsb-status-ok/.dsb-status-error (added by the .js on success/failure)
rather than an inline style, so these tokens (and dark mode) apply. */
.dsb-status {
font-family: var(--wikt-sans);
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-status-ok { color: var(--wikt-ok); }
.dsb-status-error { color: var(--wikt-danger); }
/* ── "+ Beşek lê zêde bike" ──────────────────────────────────────────
An accent-coloured action link, like the form's .wiktku-adv-toggle:
it opens something, so it is not muted grey. */
/* iOS Safari zooms the page in whenever a field it focuses has text under 16px, and everything here is
12-13px for density. On touch the form controls go up to the size that stops it; the pointer query
leaves the desktop layout alone. */
@media (pointer: coarse) {
.dsb-input,
.dsb-tag-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
font-size: 16px;
}
/* Full width, whatever the viewport reports: a phone asked to render the desktop site says ~980px and
would otherwise leave these at their desktop size. */
.dsb-subsection-panel .dsb-input,
.dsb-subsection-panel .dsb-subsection-select,
.dsb-convert-pos-panel .dsb-convert-pos-select {
width: 100%;
max-width: 100%;
}
/* Sernav is the exception: it names the group, so it stays visibly smaller than the boxes it heads.
The cap matters more than the em width -- at 16px, 12em came out the same size as a word box. */
.dsb-subsection-panel .dsb-param-row-inline .dsb-input {
width: 8em;
max-width: 40%;
}
}
/* The image rows reuse the inline row, but nothing here is optional the way a group's "Sernav" is:
a file name and a caption want the whole row. */
.dsb-resim-list .dsb-param-row-inline > .dsb-adv-label,
.dsb-resim-list .dsb-tpl-param-row > label {
display: flex;
gap: 8px;
align-items: center;
}
.dsb-resim-list .dsb-param-row-inline .dsb-input,
.dsb-resim-list .dsb-tpl-param-row .dsb-input {
flex: 1 1 auto;
width: auto;
max-width: 100%;
}
/* ── Subsection panel ────────────────────────────────────────────────
A sunken card rather than a grey utility box; fields stack so the
picker and its input are not competing for one narrow row. */
.dsb-subsection-select,
.dsb-convert-pos-select {
box-sizing: border-box;
width: 100%;
max-width: 100%;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-subsection-select:focus,
.dsb-convert-pos-select:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The template-insert picker sits above the Çavkanî textarea, not in place of it -- a little breathing
room so the two don't read as one control. */
.dsb-cavkani-picker {
margin-bottom: 6px;
}
.dsb-subsection-field {
min-width: 0;
}
.dsb-subsection-words,
.dsb-subsection-text {
box-sizing: border-box;
width: 100%;
min-width: 0;
}
.dsb-subsection-text {
line-height: 1.6;
resize: vertical;
}
/* ── Gloss field inside the advanced-editing panel ────────────────────
Same shape as .dsb-subsection-text. */
.dsb-edit-gloss {
width: 100%;
line-height: 1.6;
resize: vertical;
}
/* ── Live "not saved yet" previews ─────────────────────────────────────
Dropped into the rendered page at the change's own spot. Dashed edge + a small badge mark them as
pending, not real content; they disappear on "Betal bike" or once the real save lands. */
.dsb-preview-block,
.dsb-preview-inline {
margin: 8px 0;
padding: 8px 12px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-accent-tint);
border: 1px dashed var(--wikt-accent-edge);
border-radius: var(--wikt-r-field);
}
.dsb-preview-inline {
margin-left: 26px;
max-width: 520px;
}
.dsb-preview-heading {
margin: 0 0 4px;
font-size: 15px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-preview-badge {
display: inline-block;
margin-bottom: 6px;
padding: 1px 8px;
font-size: 11px;
font-weight: 500;
color: var(--wikt-accent-deep);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
}
/* Parser output goes in here, so it drops the panel's own font and colour and takes the page's \u2014 the
point of the box is that what is inside it looks exactly like what will be saved. */
.dsb-preview-rendered,
.dsb-preview-rendered-inline {
font-family: inherit;
font-size: 14px;
color: var(--wikt-ink);
}
.dsb-preview-rendered-inline {
display: inline;
}
.dsb-preview-rendered-inline .mw-parser-output,
.dsb-preview-rendered-inline .mw-parser-output > p {
display: inline;
}
.dsb-preview-list,
.dsb-preview-text {
margin: 0;
padding-left: 18px;
}
.dsb-preview-text {
padding-left: 0;
}
/* ── Floating save-queue widget ─────────────────────────────────────
This gadget's own small floating box. One "Lê zêde bike" click anywhere on the page adds a line here;
"Guhertinan qeyd bike" makes the one real save; "Betal bike" drops everything queued without touching the page. */
.dsb-queue-box {
position: fixed;
top: 60px;
right: 16px;
z-index: 1000;
box-sizing: border-box;
width: 280px;
max-width: calc(100vw - 32px);
max-height: calc(100vh - 90px);
overflow-y: auto;
padding: 12px 14px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent);
border-radius: var(--wikt-r-card);
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.16);
}
/* An OOUI dialog sits low in the page's stacking order (z-index 4), so no z-index here can go under it
without dropping behind the article as well. While one is open -- on a phone it fills the screen --
the box is taken off instead; wikt.cite puts the class on while its dialogs are showing. */
body.wikt-dialog-open .dsb-queue-box {
display: none;
}
.dsb-queue-title {
margin-right: 20px;
font-size: 13px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-queue-close {
float: right;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-queue-close:hover,
.dsb-queue-close:focus {
color: var(--wikt-accent-deep);
}
/* Stands in for the list before anything is queued, saying what the save button is for. */
.dsb-queue-empty {
margin: 8px 0;
font-size: 12px;
line-height: 1.5;
color: var(--wikt-ink-3);
}
.dsb-queue-list {
margin: 8px 0;
padding-left: 18px;
max-height: 160px;
overflow-y: auto;
}
.dsb-queue-list li {
margin-bottom: 4px;
}
/* Drops one queued change. Dim until its line is hovered, so the list stays a list and not a row of
buttons. */
.dsb-queue-drop {
margin-left: 6px;
padding: 0 3px;
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
opacity: 0;
transition: opacity 0.12s ease, color 0.12s ease;
}
.dsb-queue-list li:hover .dsb-queue-drop,
.dsb-queue-drop:focus {
opacity: 1;
}
.dsb-queue-drop:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* No hover on touch, so the button is simply there. */
@media (hover: none), (pointer: coarse) {
.dsb-queue-drop {
opacity: 1;
}
}
.dsb-queue-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
}
.dsb-queue-save,
.dsb-queue-cancel {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.dsb-queue-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-queue-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-queue-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-queue-cancel {
background: transparent;
border: 1px solid var(--wikt-field-edge);
color: var(--wikt-ink-3);
}
.dsb-queue-cancel:hover {
border-color: var(--wikt-ink-3);
color: var(--wikt-ink);
}
.dsb-queue-status {
margin-top: 6px;
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-queue-status.dsb-status-ok { color: var(--wikt-ok); }
.dsb-queue-status.dsb-status-error { color: var(--wikt-danger); }
/* ── Minerva (mobile) ────────────────────────────────────────────────
Narrower viewport: keep the panel on screen and give every tap
target the form's 44px minimum. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-quick-row {
font-size: 13px;
margin-left: 20px;
}
.dsb-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
min-height: 40px;
/* >=16px stops iOS Safari zooming the page on focus. */
font-size: 16px;
}
.dsb-tagfield .dsb-input {
min-height: 32px;
}
.dsb-tag-remove {
padding: 0 6px;
}
.dsb-save,
.dsb-cancel,
.dsb-autolink,
.dsb-queue-save,
.dsb-queue-cancel {
min-height: 40px;
padding: 8px 16px;
}
.dsb-queue-box {
top: auto;
bottom: 8px;
right: 8px;
left: 8px;
width: auto;
}
}
/* ── Dark mode ───────────────────────────────────────────────────────
Same warm dark palette and same explicit-signal-only rule as the
form's stylesheet: no bare prefers-color-scheme fallback, since
legacy Vector sets no theme class and would go dark on its own. */
html.skin-theme-clientpref-night .dsb-save,
html.skin-nightmode .dsb-save,
html.skin-theme-clientpref-night .dsb-queue-save,
html.skin-nightmode .dsb-queue-save {
color: #14213a;
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .dsb-save,
html.skin-theme-clientpref-os .dsb-queue-save {
color: #14213a;
}
}
/* Rules the menu's three sections apart (see MENU_GROUPS). */
.dsb-more-menu .dsb-menu-group-start {
margin-top: 7px;
padding-top: 7px;
border-top: 1px solid var(--wikt-rule-soft);
}
/* Deleting a sense is destructive, so it reads in the danger colour -- but it shares the last section
with "Maneyeke din zêde bike" (both act on the sense itself), so it carries no rule of its own. */
.dsb-more-menu .dsb-field-delete {
color: var(--wikt-danger);
}
.dsb-more-menu .dsb-field-delete:hover,
.dsb-more-menu .dsb-field-delete:focus {
color: var(--wikt-danger);
text-decoration: underline;
}
/* A sense queued for deletion, struck through until the queue is saved or the change dropped. */
li.dsb-sense-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
li.dsb-sense-doomed .dsb-quick-row {
text-decoration: none;
}
/* The word lists a not-yet-saved sense carries, and the links that add them. */
.dsb-new-sense-picker {
display: flex;
flex-wrap: wrap;
gap: 4px 12px;
margin-top: 6px;
}
.dsb-new-sense-lists:not(:empty) {
margin-top: 8px;
}
/* A quotation or example queued for removal, marked where it sits like a doomed sense. */
.dsb-example-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
opacity: 0.75;
}
/* Queue confirmation for row actions that have no field box of their own. */
.dsb-quick-note {
display: block;
margin-top: 6px;
}
/* Dropped by anything that cannot live in a 520px column: a long etymology, and a word list wide
enough to have gone to two columns. */
.dsb-subsection-panel.dsb-panel-wide {
max-width: none;
}
/* Word-list groups: one "{{kol3}}" per block, told apart only when there is more than one. */
.dsb-wordgroups-many .dsb-wordgroup {
padding-top: 10px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-wordgroups-many .dsb-wordgroup:first-child {
padding-top: 0;
border-top: 0;
}
.dsb-group-remove {
color: var(--wikt-danger);
}
/* A sernav-headed group inside an unsaved-section preview. */
.dsb-preview-subheading {
margin-top: 6px;
font-weight: 600;
}
/* ── Werger box form ──
The {{werger-ser}} parameters. Reuses .dsb-input / .dsb-adv-label from the other panels; only the
note and the disclosure body need anything of their own. */
.dsb-werger-note {
margin: 0 0 10px;
color: var(--wikt-muted);
font-size: 12px;
line-height: 1.5;
}
.dsb-werger-main {
display: flex;
flex-direction: column;
gap: 10px;
}
/* The box is nested inside its label, so it is made a block to sit under the label text instead of
beside it -- inline, a short label like "id" ends up behind its own box. */
.dsb-werger-row .dsb-adv-label {
display: block;
margin-bottom: 0;
}
.dsb-werger-row .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* ── Îzaha maneyî ──
Opens under the Mane box; the two are one gloss, so it sits tight against it. */
.dsb-izah-wrap {
margin-top: 6px;
}
.dsb-izah-wrap .dsb-adv-label {
display: block;
}
.dsb-izah-wrap .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* A sense redrawn from the change that is queued for it, or a whole new sense waiting in the list's own
numbering. No badge and no frame: the point is that it reads the way it will read once saved. The tint
is the only sign, and the queue box is what actually says nothing is published yet. */
.dsb-sense-preview {
background: var(--wikt-accent-tint);
box-shadow: -3px 0 0 var(--wikt-accent-edge);
}
/* Kept in the tree so another gadget's form still has somewhere to open, but out of sight while the
preview above draws the same content. */
.dsb-preview-hidden {
display: none !important;
}
li.dsb-sense-preview {
display: list-item;
padding-left: 4px;
}
/* </nowiki> */
alj6u0v2w46e23236vxh9kvtzt8pdqz
6534982
6534977
2026-09-20T19:19:24Z
Wikihez
7344
DefSideBoxes: the new sense gets its own numbered row, its own buttons and its own quotations
6534982
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.css]]
*/
/* ── Searchable select ───────────────────────────────────────────────
The native <select> is hidden inside the wrapper and still holds the value; the input filters the list. */
.dsb-ac-wrap {
position: relative;
display: block;
}
.dsb-ac-input {
width: 100%;
box-sizing: border-box;
}
.dsb-ac-results {
position: absolute;
z-index: 30;
top: 100%;
left: 0;
right: 0;
max-height: 260px;
margin: 2px 0 0;
padding: 4px;
overflow-y: auto;
list-style: none;
font-family: var(--wikt-sans);
font-size: 13px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
.dsb-ac-results li {
padding: 6px 8px;
border-radius: 6px;
cursor: pointer;
}
.dsb-ac-results li:hover,
.dsb-ac-results li.dsb-ac-active {
background: var(--wikt-accent-tint);
}
/* ── Etîmolojî editing surface ───────────────────────────────────────
A contenteditable line of wikitext: text is typed straight into it, template calls sit in it as chips. */
.dsb-tpl-surface {
min-height: 72px;
margin-top: 6px;
padding: 8px 10px;
font-family: var(--wikt-sans);
font-size: 14px;
line-height: 1.6;
color: var(--wikt-ink);
white-space: pre-wrap;
word-wrap: break-word;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-tpl-surface:focus {
outline: none;
border-color: var(--wikt-accent);
box-shadow: 0 0 0 2px var(--wikt-accent-tint);
}
/* The chips: visibly not prose, and atomic to the caret. */
/* Takes the call off the line without going through the parameter dialog -- on a phone there is no
reliable backspace onto a contenteditable="false" chip. */
.dsb-tpl-chip-remove {
position: relative;
margin-left: 5px;
padding: 0 2px;
font-family: var(--wikt-sans);
font-size: 13px;
line-height: 1;
color: var(--wikt-accent-deep);
opacity: 0.6;
cursor: pointer;
}
.dsb-tpl-chip-remove:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* The glyph stays small; the tap target grows invisibly around it. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-tpl-chip-remove {
opacity: 1;
}
.dsb-tpl-chip-remove::after {
content: "";
position: absolute;
inset: -10px -8px;
}
}
.dsb-tpl-chip {
display: inline-block;
margin: 0 1px;
padding: 1px 6px;
font-family: var(--wikt-mono);
font-size: 12.5px;
color: var(--wikt-accent-deep);
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
vertical-align: baseline;
background: var(--wikt-accent-tint);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
touch-action: manipulation;
cursor: pointer;
user-select: none;
}
.dsb-tpl-chip:hover,
.dsb-tpl-chip:focus {
background: var(--wikt-field-bg);
border-color: var(--wikt-accent);
outline: none;
}
/* ── Template parameter form ─────────────────────────────────────────
.dsb-tpl-name/-desc/-param-row/-help/-undocumented/-modifier-list and the deprecated-label override now
live in cite.css (wikt.cite's own stylesheet): buildTemplateParamForm() moved there so start.js's own
Etîmolojî/Çavkanî pickers can open the same dialog. */
/* One-line variant: label beside its box rather than above it, for a single field whose name would
otherwise read as a heading for everything below it (the word groups' "Sernav"). */
.dsb-param-row-inline > .dsb-adv-label {
display: flex;
gap: 8px;
align-items: center;
}
/* Sernav is optional and names the group; the word boxes under it are what the form is actually for.
Letting it stretch to the full row made the one thing you may leave blank the widest box on the
card, so it takes only the space its own content needs. */
.dsb-param-row-inline .dsb-input {
flex: 0 1 auto;
width: 12em;
max-width: 40%;
/* .dsb-input's own 16em floor would win over the width above and make this the widest box on the
card, which is the opposite of the point. */
min-width: 0;
max-width: 100%;
min-width: 0;
}
.dsb-gloss-raw {
font-size: 12px;
}
/* Disclosure arrow on "Hevmane û Bêhtir", flipped when the menu is open. */
.dsb-more-arrow {
font-size: 10px;
vertical-align: 1px;
}
/* The POS panel's follow-up questions (cinsiyet / lektur): label above its select, like the field boxes. */
.dsb-pos-param-row {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 8px;
}
/* ── "Guhartina pêşketî" (advanced editing) panel ──────────────────────
Revealed by the pencil toggle above: every per-sense action this gadget offers, queued together with one
"Lê zêde bike". A plain inline row, not a bordered/background card. */
.dsb-quick-row {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
box-sizing: border-box;
max-width: 520px;
margin: 4px 0 0 16px;
font-family: var(--wikt-sans);
font-size: 12px;
color: var(--wikt-ink-2);
}
.dsb-quick-link {
align-self: flex-start;
margin-bottom: 10px;
font-size: 12px;
color: var(--wikt-accent);
text-decoration: none;
white-space: nowrap;
cursor: pointer;
}
/* The links under a word list ("+ Komek lê zêde bike", "Wekî wîkîtext biguhêre") sit in the field wrap
rather than in a flex column, so they need their own spacing or they run together. */
.dsb-subsection-field > .dsb-quick-link {
display: inline-block;
margin-right: 14px;
}
.dsb-quick-link:hover,
.dsb-quick-link:focus {
color: var(--wikt-accent-deep);
text-decoration: underline;
}
/* The picker row: gloss + the two primary word groups as direct "+ <field>" links, plus a "Hevmane û Bêhtir" toggle
for the rest -- clicking a link opens its box above the buttons and hides the link; .dsb-box-remove un-hides it.
position: relative anchors .dsb-more-menu's overlay below (see there). */
.dsb-field-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 4px 14px;
}
.dsb-field-picker .dsb-quick-link {
margin-bottom: 0;
}
/* Under the buttons once a box is open: a little space so the links do not read as part of the buttons. */
.dsb-open-boxes:not(:empty) ~ .dsb-field-picker {
margin-top: 8px;
}
/* The "Hevmane û Bêhtir" dropdown: every non-primary field link (plus Nimûne/jêgirtin, which sits first) stacked as a
plain vertical list. Floats over the page as an overlay (positioned against .dsb-field-picker) instead of
pushing $openBoxes/the rest of the definition down when it opens. */
.dsb-more-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 10;
display: flex;
flex-direction: column;
align-items: flex-start;
width: max-content;
max-width: min(320px, calc(100vw - 40px));
gap: 6px;
margin: 2px 0 0 4px;
padding: 8px 12px;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-field);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
}
.dsb-more-menu .dsb-quick-link {
margin-bottom: 0;
}
/* Boxes the user has actually opened, stacked in the order they were added -- several can be open at once. */
.dsb-open-boxes {
display: flex;
flex-direction: column;
}
/* Nothing open: take it out of the flow so the links do not sit above an empty gap. */
.dsb-open-boxes:empty {
display: none;
}
/* One field (its label + input) within the panel -- always a currently-open, removable box. */
.dsb-adv-group {
position: relative;
margin-bottom: 14px;
padding-right: 20px;
}
.dsb-adv-label {
margin-bottom: 4px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
/* The example sentence beside a word list's name: same text the wizard shows, so it reads as the
quieter half of the label rather than a second heading. */
.dsb-adv-hint {
margin-left: 6px;
font-weight: 400;
font-style: italic;
color: var(--wikt-muted);
}
/* The shared image rows (util.js's buildImageListField): one label + box per line, like the parameter
rows above them. Three identical boxes twice over is unreadable without a break between them, so each
image is its own block with its own name. */
.dsb-resim-list {
margin: 0;
padding-left: 0;
}
.dsb-resim-item {
list-style: none;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-resim-item:first-child {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.dsb-resim-index {
margin-bottom: 6px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
.dsb-box-remove {
position: absolute;
top: 0;
right: 0;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-box-remove:hover,
.dsb-box-remove:focus {
color: var(--wikt-accent-deep);
}
.dsb-form-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.dsb-input {
box-sizing: border-box;
min-width: 16em;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
/* The native corner is replaced by util.js's grip (see wikt-edit.css), which drags vertically only and
remembers the height. */
.dsb-subsection-panel textarea.dsb-input,
.dsb-adv-group textarea.dsb-input {
resize: none;
}
.dsb-input::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
.dsb-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* ── Plain tag field (Etîket, Kategorî) ────────────────────────────────
Plain type-and-commit tags with no suggestion panel; unlike Hevmane/Dijmane/etc., these carry no modifiers. */
.dsb-tagfield {
box-sizing: border-box;
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
min-width: 16em;
padding: 4px 6px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.dsb-tagfield:focus-within {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
}
.dsb-tag {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
padding: 3px 6px 3px 10px;
}
.dsb-tag-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
.dsb-tag-remove:hover {
color: var(--wikt-danger);
}
.dsb-tagfield .dsb-input {
flex: 1 1 8em;
min-width: 8em;
border: 0;
padding: 3px 2px;
background: transparent;
box-shadow: none;
}
.dsb-tagfield .dsb-input:focus {
box-shadow: none;
}
.dsb-save,
.dsb-cancel,
.dsb-delete,
.dsb-autolink {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
/* One filled pill per row — the same rule the form's footer follows. */
.dsb-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-cancel {
background: transparent;
border: 1px solid transparent;
color: var(--wikt-ink-3);
}
.dsb-cancel:hover {
border-color: var(--wikt-field-edge);
color: var(--wikt-ink);
}
/* Removing a heading: plain like Betal bike, in the danger colour, pushed to the far end of the row. */
.dsb-delete {
margin-left: auto;
background: transparent;
border: 1px solid transparent;
color: var(--wikt-danger);
}
.dsb-delete:hover {
border-color: var(--wikt-danger);
}
/* The heading's name, above the section's own box. */
.dsb-heading-name-wrap {
margin-bottom: 10px;
}
/* A heading queued for removal, with everything it owns, until the queue is saved or the change dropped. */
.dsb-preview-removed {
opacity: 0.5;
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
/* An outline pill, distinct from both the filled .dsb-save and the plain-text .dsb-cancel -- a secondary
utility action, not the primary save nor a dismissal. */
.dsb-autolink {
background: transparent;
border: 1px solid var(--wikt-accent-edge);
color: var(--wikt-accent);
}
.dsb-autolink:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-autolink:disabled {
opacity: 0.55;
cursor: progress;
}
/* Status line. Color comes from .dsb-status-ok/.dsb-status-error (added by the .js on success/failure)
rather than an inline style, so these tokens (and dark mode) apply. */
.dsb-status {
font-family: var(--wikt-sans);
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-status-ok { color: var(--wikt-ok); }
.dsb-status-error { color: var(--wikt-danger); }
/* ── "+ Beşek lê zêde bike" ──────────────────────────────────────────
An accent-coloured action link, like the form's .wiktku-adv-toggle:
it opens something, so it is not muted grey. */
/* iOS Safari zooms the page in whenever a field it focuses has text under 16px, and everything here is
12-13px for density. On touch the form controls go up to the size that stops it; the pointer query
leaves the desktop layout alone. */
@media (pointer: coarse) {
.dsb-input,
.dsb-tag-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
font-size: 16px;
}
/* Full width, whatever the viewport reports: a phone asked to render the desktop site says ~980px and
would otherwise leave these at their desktop size. */
.dsb-subsection-panel .dsb-input,
.dsb-subsection-panel .dsb-subsection-select,
.dsb-convert-pos-panel .dsb-convert-pos-select {
width: 100%;
max-width: 100%;
}
/* Sernav is the exception: it names the group, so it stays visibly smaller than the boxes it heads.
The cap matters more than the em width -- at 16px, 12em came out the same size as a word box. */
.dsb-subsection-panel .dsb-param-row-inline .dsb-input {
width: 8em;
max-width: 40%;
}
}
/* The image rows reuse the inline row, but nothing here is optional the way a group's "Sernav" is:
a file name and a caption want the whole row. */
.dsb-resim-list .dsb-param-row-inline > .dsb-adv-label,
.dsb-resim-list .dsb-tpl-param-row > label {
display: flex;
gap: 8px;
align-items: center;
}
.dsb-resim-list .dsb-param-row-inline .dsb-input,
.dsb-resim-list .dsb-tpl-param-row .dsb-input {
flex: 1 1 auto;
width: auto;
max-width: 100%;
}
/* ── Subsection panel ────────────────────────────────────────────────
A sunken card rather than a grey utility box; fields stack so the
picker and its input are not competing for one narrow row. */
.dsb-subsection-select,
.dsb-convert-pos-select {
box-sizing: border-box;
width: 100%;
max-width: 100%;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-subsection-select:focus,
.dsb-convert-pos-select:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The template-insert picker sits above the Çavkanî textarea, not in place of it -- a little breathing
room so the two don't read as one control. */
.dsb-cavkani-picker {
margin-bottom: 6px;
}
.dsb-subsection-field {
min-width: 0;
}
.dsb-subsection-words,
.dsb-subsection-text {
box-sizing: border-box;
width: 100%;
min-width: 0;
}
.dsb-subsection-text {
line-height: 1.6;
resize: vertical;
}
/* ── Gloss field inside the advanced-editing panel ────────────────────
Same shape as .dsb-subsection-text. */
.dsb-edit-gloss {
width: 100%;
line-height: 1.6;
resize: vertical;
}
/* ── Live "not saved yet" previews ─────────────────────────────────────
Dropped into the rendered page at the change's own spot. Dashed edge + a small badge mark them as
pending, not real content; they disappear on "Betal bike" or once the real save lands. */
.dsb-preview-block,
.dsb-preview-inline {
margin: 8px 0;
padding: 8px 12px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-accent-tint);
border: 1px dashed var(--wikt-accent-edge);
border-radius: var(--wikt-r-field);
}
.dsb-preview-inline {
margin-left: 26px;
max-width: 520px;
}
.dsb-preview-heading {
margin: 0 0 4px;
font-size: 15px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-preview-badge {
display: inline-block;
margin-bottom: 6px;
padding: 1px 8px;
font-size: 11px;
font-weight: 500;
color: var(--wikt-accent-deep);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
}
/* Parser output goes in here, so it drops the panel's own font and colour and takes the page's \u2014 the
point of the box is that what is inside it looks exactly like what will be saved. */
.dsb-preview-rendered,
.dsb-preview-rendered-inline {
font-family: inherit;
font-size: 14px;
color: var(--wikt-ink);
}
.dsb-preview-rendered-inline {
display: inline;
}
.dsb-preview-rendered-inline .mw-parser-output,
.dsb-preview-rendered-inline .mw-parser-output > p {
display: inline;
}
.dsb-preview-list,
.dsb-preview-text {
margin: 0;
padding-left: 18px;
}
.dsb-preview-text {
padding-left: 0;
}
/* ── Floating save-queue widget ─────────────────────────────────────
This gadget's own small floating box. One "Lê zêde bike" click anywhere on the page adds a line here;
"Guhertinan qeyd bike" makes the one real save; "Betal bike" drops everything queued without touching the page. */
.dsb-queue-box {
position: fixed;
top: 60px;
right: 16px;
z-index: 1000;
box-sizing: border-box;
width: 280px;
max-width: calc(100vw - 32px);
max-height: calc(100vh - 90px);
overflow-y: auto;
padding: 12px 14px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent);
border-radius: var(--wikt-r-card);
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.16);
}
/* An OOUI dialog sits low in the page's stacking order (z-index 4), so no z-index here can go under it
without dropping behind the article as well. While one is open -- on a phone it fills the screen --
the box is taken off instead; wikt.cite puts the class on while its dialogs are showing. */
body.wikt-dialog-open .dsb-queue-box {
display: none;
}
.dsb-queue-title {
margin-right: 20px;
font-size: 13px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-queue-close {
float: right;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-queue-close:hover,
.dsb-queue-close:focus {
color: var(--wikt-accent-deep);
}
/* Stands in for the list before anything is queued, saying what the save button is for. */
.dsb-queue-empty {
margin: 8px 0;
font-size: 12px;
line-height: 1.5;
color: var(--wikt-ink-3);
}
.dsb-queue-list {
margin: 8px 0;
padding-left: 18px;
max-height: 160px;
overflow-y: auto;
}
.dsb-queue-list li {
margin-bottom: 4px;
}
/* Drops one queued change. Dim until its line is hovered, so the list stays a list and not a row of
buttons. */
.dsb-queue-drop {
margin-left: 6px;
padding: 0 3px;
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
opacity: 0;
transition: opacity 0.12s ease, color 0.12s ease;
}
.dsb-queue-list li:hover .dsb-queue-drop,
.dsb-queue-drop:focus {
opacity: 1;
}
.dsb-queue-drop:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* No hover on touch, so the button is simply there. */
@media (hover: none), (pointer: coarse) {
.dsb-queue-drop {
opacity: 1;
}
}
.dsb-queue-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
}
.dsb-queue-save,
.dsb-queue-cancel {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.dsb-queue-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-queue-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-queue-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-queue-cancel {
background: transparent;
border: 1px solid var(--wikt-field-edge);
color: var(--wikt-ink-3);
}
.dsb-queue-cancel:hover {
border-color: var(--wikt-ink-3);
color: var(--wikt-ink);
}
.dsb-queue-status {
margin-top: 6px;
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-queue-status.dsb-status-ok { color: var(--wikt-ok); }
.dsb-queue-status.dsb-status-error { color: var(--wikt-danger); }
/* ── Minerva (mobile) ────────────────────────────────────────────────
Narrower viewport: keep the panel on screen and give every tap
target the form's 44px minimum. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-quick-row {
font-size: 13px;
margin-left: 20px;
}
.dsb-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
min-height: 40px;
/* >=16px stops iOS Safari zooming the page on focus. */
font-size: 16px;
}
.dsb-tagfield .dsb-input {
min-height: 32px;
}
.dsb-tag-remove {
padding: 0 6px;
}
.dsb-save,
.dsb-cancel,
.dsb-autolink,
.dsb-queue-save,
.dsb-queue-cancel {
min-height: 40px;
padding: 8px 16px;
}
.dsb-queue-box {
top: auto;
bottom: 8px;
right: 8px;
left: 8px;
width: auto;
}
}
/* ── Dark mode ───────────────────────────────────────────────────────
Same warm dark palette and same explicit-signal-only rule as the
form's stylesheet: no bare prefers-color-scheme fallback, since
legacy Vector sets no theme class and would go dark on its own. */
html.skin-theme-clientpref-night .dsb-save,
html.skin-nightmode .dsb-save,
html.skin-theme-clientpref-night .dsb-queue-save,
html.skin-nightmode .dsb-queue-save {
color: #14213a;
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .dsb-save,
html.skin-theme-clientpref-os .dsb-queue-save {
color: #14213a;
}
}
/* Rules the menu's three sections apart (see MENU_GROUPS). */
.dsb-more-menu .dsb-menu-group-start {
margin-top: 7px;
padding-top: 7px;
border-top: 1px solid var(--wikt-rule-soft);
}
/* Deleting a sense is destructive, so it reads in the danger colour -- but it shares the last section
with "Maneyeke din zêde bike" (both act on the sense itself), so it carries no rule of its own. */
.dsb-more-menu .dsb-field-delete {
color: var(--wikt-danger);
}
.dsb-more-menu .dsb-field-delete:hover,
.dsb-more-menu .dsb-field-delete:focus {
color: var(--wikt-danger);
text-decoration: underline;
}
/* A sense queued for deletion, struck through until the queue is saved or the change dropped. */
li.dsb-sense-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
li.dsb-sense-doomed .dsb-quick-row {
text-decoration: none;
}
/* The row a new sense gets before it is saved: numbered by the list it sits in, with its own box. */
.dsb-new-sense-row .dsb-new-sense {
margin: 4px 0 8px;
}
.dsb-new-sense-quote {
margin-top: 4px;
padding: 4px 6px;
border-radius: var(--wikt-r-field, 6px);
background: var(--wikt-code-bg);
font-family: monospace;
font-size: 12px;
overflow-wrap: anywhere;
}
/* The word lists a not-yet-saved sense carries, and the links that add them. */
.dsb-new-sense-picker {
display: flex;
flex-wrap: wrap;
gap: 4px 12px;
margin-top: 6px;
}
.dsb-new-sense-lists:not(:empty) {
margin-top: 8px;
}
/* A quotation or example queued for removal, marked where it sits like a doomed sense. */
.dsb-example-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
opacity: 0.75;
}
/* Queue confirmation for row actions that have no field box of their own. */
.dsb-quick-note {
display: block;
margin-top: 6px;
}
/* Dropped by anything that cannot live in a 520px column: a long etymology, and a word list wide
enough to have gone to two columns. */
.dsb-subsection-panel.dsb-panel-wide {
max-width: none;
}
/* Word-list groups: one "{{kol3}}" per block, told apart only when there is more than one. */
.dsb-wordgroups-many .dsb-wordgroup {
padding-top: 10px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-wordgroups-many .dsb-wordgroup:first-child {
padding-top: 0;
border-top: 0;
}
.dsb-group-remove {
color: var(--wikt-danger);
}
/* A sernav-headed group inside an unsaved-section preview. */
.dsb-preview-subheading {
margin-top: 6px;
font-weight: 600;
}
/* ── Werger box form ──
The {{werger-ser}} parameters. Reuses .dsb-input / .dsb-adv-label from the other panels; only the
note and the disclosure body need anything of their own. */
.dsb-werger-note {
margin: 0 0 10px;
color: var(--wikt-muted);
font-size: 12px;
line-height: 1.5;
}
.dsb-werger-main {
display: flex;
flex-direction: column;
gap: 10px;
}
/* The box is nested inside its label, so it is made a block to sit under the label text instead of
beside it -- inline, a short label like "id" ends up behind its own box. */
.dsb-werger-row .dsb-adv-label {
display: block;
margin-bottom: 0;
}
.dsb-werger-row .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* ── Îzaha maneyî ──
Opens under the Mane box; the two are one gloss, so it sits tight against it. */
.dsb-izah-wrap {
margin-top: 6px;
}
.dsb-izah-wrap .dsb-adv-label {
display: block;
}
.dsb-izah-wrap .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* A sense redrawn from the change that is queued for it, or a whole new sense waiting in the list's own
numbering. No badge and no frame: the point is that it reads the way it will read once saved. The tint
is the only sign, and the queue box is what actually says nothing is published yet. */
.dsb-sense-preview {
background: var(--wikt-accent-tint);
box-shadow: -3px 0 0 var(--wikt-accent-edge);
}
/* Kept in the tree so another gadget's form still has somewhere to open, but out of sight while the
preview above draws the same content. */
.dsb-preview-hidden {
display: none !important;
}
li.dsb-sense-preview {
display: list-item;
padding-left: 4px;
}
/* </nowiki> */
5amv5gorz214v8vuc36eqrcmygn26rh
6534997
6534982
2026-09-20T19:43:15Z
Wikihez
7344
DefSideBoxes: the new-sense row takes the same actions row a saved sense has
6534997
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.css]]
*/
/* ── Searchable select ───────────────────────────────────────────────
The native <select> is hidden inside the wrapper and still holds the value; the input filters the list. */
.dsb-ac-wrap {
position: relative;
display: block;
}
.dsb-ac-input {
width: 100%;
box-sizing: border-box;
}
.dsb-ac-results {
position: absolute;
z-index: 30;
top: 100%;
left: 0;
right: 0;
max-height: 260px;
margin: 2px 0 0;
padding: 4px;
overflow-y: auto;
list-style: none;
font-family: var(--wikt-sans);
font-size: 13px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
.dsb-ac-results li {
padding: 6px 8px;
border-radius: 6px;
cursor: pointer;
}
.dsb-ac-results li:hover,
.dsb-ac-results li.dsb-ac-active {
background: var(--wikt-accent-tint);
}
/* ── Etîmolojî editing surface ───────────────────────────────────────
A contenteditable line of wikitext: text is typed straight into it, template calls sit in it as chips. */
.dsb-tpl-surface {
min-height: 72px;
margin-top: 6px;
padding: 8px 10px;
font-family: var(--wikt-sans);
font-size: 14px;
line-height: 1.6;
color: var(--wikt-ink);
white-space: pre-wrap;
word-wrap: break-word;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-tpl-surface:focus {
outline: none;
border-color: var(--wikt-accent);
box-shadow: 0 0 0 2px var(--wikt-accent-tint);
}
/* The chips: visibly not prose, and atomic to the caret. */
/* Takes the call off the line without going through the parameter dialog -- on a phone there is no
reliable backspace onto a contenteditable="false" chip. */
.dsb-tpl-chip-remove {
position: relative;
margin-left: 5px;
padding: 0 2px;
font-family: var(--wikt-sans);
font-size: 13px;
line-height: 1;
color: var(--wikt-accent-deep);
opacity: 0.6;
cursor: pointer;
}
.dsb-tpl-chip-remove:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* The glyph stays small; the tap target grows invisibly around it. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-tpl-chip-remove {
opacity: 1;
}
.dsb-tpl-chip-remove::after {
content: "";
position: absolute;
inset: -10px -8px;
}
}
.dsb-tpl-chip {
display: inline-block;
margin: 0 1px;
padding: 1px 6px;
font-family: var(--wikt-mono);
font-size: 12.5px;
color: var(--wikt-accent-deep);
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
vertical-align: baseline;
background: var(--wikt-accent-tint);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
touch-action: manipulation;
cursor: pointer;
user-select: none;
}
.dsb-tpl-chip:hover,
.dsb-tpl-chip:focus {
background: var(--wikt-field-bg);
border-color: var(--wikt-accent);
outline: none;
}
/* ── Template parameter form ─────────────────────────────────────────
.dsb-tpl-name/-desc/-param-row/-help/-undocumented/-modifier-list and the deprecated-label override now
live in cite.css (wikt.cite's own stylesheet): buildTemplateParamForm() moved there so start.js's own
Etîmolojî/Çavkanî pickers can open the same dialog. */
/* One-line variant: label beside its box rather than above it, for a single field whose name would
otherwise read as a heading for everything below it (the word groups' "Sernav"). */
.dsb-param-row-inline > .dsb-adv-label {
display: flex;
gap: 8px;
align-items: center;
}
/* Sernav is optional and names the group; the word boxes under it are what the form is actually for.
Letting it stretch to the full row made the one thing you may leave blank the widest box on the
card, so it takes only the space its own content needs. */
.dsb-param-row-inline .dsb-input {
flex: 0 1 auto;
width: 12em;
max-width: 40%;
/* .dsb-input's own 16em floor would win over the width above and make this the widest box on the
card, which is the opposite of the point. */
min-width: 0;
max-width: 100%;
min-width: 0;
}
.dsb-gloss-raw {
font-size: 12px;
}
/* Disclosure arrow on "Hevmane û Bêhtir", flipped when the menu is open. */
.dsb-more-arrow {
font-size: 10px;
vertical-align: 1px;
}
/* The POS panel's follow-up questions (cinsiyet / lektur): label above its select, like the field boxes. */
.dsb-pos-param-row {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 8px;
}
/* ── "Guhartina pêşketî" (advanced editing) panel ──────────────────────
Revealed by the pencil toggle above: every per-sense action this gadget offers, queued together with one
"Lê zêde bike". A plain inline row, not a bordered/background card. */
.dsb-quick-row {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
box-sizing: border-box;
max-width: 520px;
margin: 4px 0 0 16px;
font-family: var(--wikt-sans);
font-size: 12px;
color: var(--wikt-ink-2);
}
.dsb-quick-link {
align-self: flex-start;
margin-bottom: 10px;
font-size: 12px;
color: var(--wikt-accent);
text-decoration: none;
white-space: nowrap;
cursor: pointer;
}
/* The links under a word list ("+ Komek lê zêde bike", "Wekî wîkîtext biguhêre") sit in the field wrap
rather than in a flex column, so they need their own spacing or they run together. */
.dsb-subsection-field > .dsb-quick-link {
display: inline-block;
margin-right: 14px;
}
.dsb-quick-link:hover,
.dsb-quick-link:focus {
color: var(--wikt-accent-deep);
text-decoration: underline;
}
/* The picker row: gloss + the two primary word groups as direct "+ <field>" links, plus a "Hevmane û Bêhtir" toggle
for the rest -- clicking a link opens its box above the buttons and hides the link; .dsb-box-remove un-hides it.
position: relative anchors .dsb-more-menu's overlay below (see there). */
.dsb-field-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 4px 14px;
}
.dsb-field-picker .dsb-quick-link {
margin-bottom: 0;
}
/* Under the buttons once a box is open: a little space so the links do not read as part of the buttons. */
.dsb-open-boxes:not(:empty) ~ .dsb-field-picker {
margin-top: 8px;
}
/* The "Hevmane û Bêhtir" dropdown: every non-primary field link (plus Nimûne/jêgirtin, which sits first) stacked as a
plain vertical list. Floats over the page as an overlay (positioned against .dsb-field-picker) instead of
pushing $openBoxes/the rest of the definition down when it opens. */
.dsb-more-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 10;
display: flex;
flex-direction: column;
align-items: flex-start;
width: max-content;
max-width: min(320px, calc(100vw - 40px));
gap: 6px;
margin: 2px 0 0 4px;
padding: 8px 12px;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-field);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
}
.dsb-more-menu .dsb-quick-link {
margin-bottom: 0;
}
/* Boxes the user has actually opened, stacked in the order they were added -- several can be open at once. */
.dsb-open-boxes {
display: flex;
flex-direction: column;
}
/* Nothing open: take it out of the flow so the links do not sit above an empty gap. */
.dsb-open-boxes:empty {
display: none;
}
/* One field (its label + input) within the panel -- always a currently-open, removable box. */
.dsb-adv-group {
position: relative;
margin-bottom: 14px;
padding-right: 20px;
}
.dsb-adv-label {
margin-bottom: 4px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
/* The example sentence beside a word list's name: same text the wizard shows, so it reads as the
quieter half of the label rather than a second heading. */
.dsb-adv-hint {
margin-left: 6px;
font-weight: 400;
font-style: italic;
color: var(--wikt-muted);
}
/* The shared image rows (util.js's buildImageListField): one label + box per line, like the parameter
rows above them. Three identical boxes twice over is unreadable without a break between them, so each
image is its own block with its own name. */
.dsb-resim-list {
margin: 0;
padding-left: 0;
}
.dsb-resim-item {
list-style: none;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-resim-item:first-child {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.dsb-resim-index {
margin-bottom: 6px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
.dsb-box-remove {
position: absolute;
top: 0;
right: 0;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-box-remove:hover,
.dsb-box-remove:focus {
color: var(--wikt-accent-deep);
}
.dsb-form-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.dsb-input {
box-sizing: border-box;
min-width: 16em;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
/* The native corner is replaced by util.js's grip (see wikt-edit.css), which drags vertically only and
remembers the height. */
.dsb-subsection-panel textarea.dsb-input,
.dsb-adv-group textarea.dsb-input {
resize: none;
}
.dsb-input::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
.dsb-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* ── Plain tag field (Etîket, Kategorî) ────────────────────────────────
Plain type-and-commit tags with no suggestion panel; unlike Hevmane/Dijmane/etc., these carry no modifiers. */
.dsb-tagfield {
box-sizing: border-box;
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
min-width: 16em;
padding: 4px 6px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.dsb-tagfield:focus-within {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
}
.dsb-tag {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
padding: 3px 6px 3px 10px;
}
.dsb-tag-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
.dsb-tag-remove:hover {
color: var(--wikt-danger);
}
.dsb-tagfield .dsb-input {
flex: 1 1 8em;
min-width: 8em;
border: 0;
padding: 3px 2px;
background: transparent;
box-shadow: none;
}
.dsb-tagfield .dsb-input:focus {
box-shadow: none;
}
.dsb-save,
.dsb-cancel,
.dsb-delete,
.dsb-autolink {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
/* One filled pill per row — the same rule the form's footer follows. */
.dsb-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-cancel {
background: transparent;
border: 1px solid transparent;
color: var(--wikt-ink-3);
}
.dsb-cancel:hover {
border-color: var(--wikt-field-edge);
color: var(--wikt-ink);
}
/* Removing a heading: plain like Betal bike, in the danger colour, pushed to the far end of the row. */
.dsb-delete {
margin-left: auto;
background: transparent;
border: 1px solid transparent;
color: var(--wikt-danger);
}
.dsb-delete:hover {
border-color: var(--wikt-danger);
}
/* The heading's name, above the section's own box. */
.dsb-heading-name-wrap {
margin-bottom: 10px;
}
/* A heading queued for removal, with everything it owns, until the queue is saved or the change dropped. */
.dsb-preview-removed {
opacity: 0.5;
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
/* An outline pill, distinct from both the filled .dsb-save and the plain-text .dsb-cancel -- a secondary
utility action, not the primary save nor a dismissal. */
.dsb-autolink {
background: transparent;
border: 1px solid var(--wikt-accent-edge);
color: var(--wikt-accent);
}
.dsb-autolink:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-autolink:disabled {
opacity: 0.55;
cursor: progress;
}
/* Status line. Color comes from .dsb-status-ok/.dsb-status-error (added by the .js on success/failure)
rather than an inline style, so these tokens (and dark mode) apply. */
.dsb-status {
font-family: var(--wikt-sans);
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-status-ok { color: var(--wikt-ok); }
.dsb-status-error { color: var(--wikt-danger); }
/* ── "+ Beşek lê zêde bike" ──────────────────────────────────────────
An accent-coloured action link, like the form's .wiktku-adv-toggle:
it opens something, so it is not muted grey. */
/* iOS Safari zooms the page in whenever a field it focuses has text under 16px, and everything here is
12-13px for density. On touch the form controls go up to the size that stops it; the pointer query
leaves the desktop layout alone. */
@media (pointer: coarse) {
.dsb-input,
.dsb-tag-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
font-size: 16px;
}
/* Full width, whatever the viewport reports: a phone asked to render the desktop site says ~980px and
would otherwise leave these at their desktop size. */
.dsb-subsection-panel .dsb-input,
.dsb-subsection-panel .dsb-subsection-select,
.dsb-convert-pos-panel .dsb-convert-pos-select {
width: 100%;
max-width: 100%;
}
/* Sernav is the exception: it names the group, so it stays visibly smaller than the boxes it heads.
The cap matters more than the em width -- at 16px, 12em came out the same size as a word box. */
.dsb-subsection-panel .dsb-param-row-inline .dsb-input {
width: 8em;
max-width: 40%;
}
}
/* The image rows reuse the inline row, but nothing here is optional the way a group's "Sernav" is:
a file name and a caption want the whole row. */
.dsb-resim-list .dsb-param-row-inline > .dsb-adv-label,
.dsb-resim-list .dsb-tpl-param-row > label {
display: flex;
gap: 8px;
align-items: center;
}
.dsb-resim-list .dsb-param-row-inline .dsb-input,
.dsb-resim-list .dsb-tpl-param-row .dsb-input {
flex: 1 1 auto;
width: auto;
max-width: 100%;
}
/* ── Subsection panel ────────────────────────────────────────────────
A sunken card rather than a grey utility box; fields stack so the
picker and its input are not competing for one narrow row. */
.dsb-subsection-select,
.dsb-convert-pos-select {
box-sizing: border-box;
width: 100%;
max-width: 100%;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-subsection-select:focus,
.dsb-convert-pos-select:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The template-insert picker sits above the Çavkanî textarea, not in place of it -- a little breathing
room so the two don't read as one control. */
.dsb-cavkani-picker {
margin-bottom: 6px;
}
.dsb-subsection-field {
min-width: 0;
}
.dsb-subsection-words,
.dsb-subsection-text {
box-sizing: border-box;
width: 100%;
min-width: 0;
}
.dsb-subsection-text {
line-height: 1.6;
resize: vertical;
}
/* ── Gloss field inside the advanced-editing panel ────────────────────
Same shape as .dsb-subsection-text. */
.dsb-edit-gloss {
width: 100%;
line-height: 1.6;
resize: vertical;
}
/* ── Live "not saved yet" previews ─────────────────────────────────────
Dropped into the rendered page at the change's own spot. Dashed edge + a small badge mark them as
pending, not real content; they disappear on "Betal bike" or once the real save lands. */
.dsb-preview-block,
.dsb-preview-inline {
margin: 8px 0;
padding: 8px 12px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-accent-tint);
border: 1px dashed var(--wikt-accent-edge);
border-radius: var(--wikt-r-field);
}
.dsb-preview-inline {
margin-left: 26px;
max-width: 520px;
}
.dsb-preview-heading {
margin: 0 0 4px;
font-size: 15px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-preview-badge {
display: inline-block;
margin-bottom: 6px;
padding: 1px 8px;
font-size: 11px;
font-weight: 500;
color: var(--wikt-accent-deep);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
}
/* Parser output goes in here, so it drops the panel's own font and colour and takes the page's \u2014 the
point of the box is that what is inside it looks exactly like what will be saved. */
.dsb-preview-rendered,
.dsb-preview-rendered-inline {
font-family: inherit;
font-size: 14px;
color: var(--wikt-ink);
}
.dsb-preview-rendered-inline {
display: inline;
}
.dsb-preview-rendered-inline .mw-parser-output,
.dsb-preview-rendered-inline .mw-parser-output > p {
display: inline;
}
.dsb-preview-list,
.dsb-preview-text {
margin: 0;
padding-left: 18px;
}
.dsb-preview-text {
padding-left: 0;
}
/* ── Floating save-queue widget ─────────────────────────────────────
This gadget's own small floating box. One "Lê zêde bike" click anywhere on the page adds a line here;
"Guhertinan qeyd bike" makes the one real save; "Betal bike" drops everything queued without touching the page. */
.dsb-queue-box {
position: fixed;
top: 60px;
right: 16px;
z-index: 1000;
box-sizing: border-box;
width: 280px;
max-width: calc(100vw - 32px);
max-height: calc(100vh - 90px);
overflow-y: auto;
padding: 12px 14px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent);
border-radius: var(--wikt-r-card);
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.16);
}
/* An OOUI dialog sits low in the page's stacking order (z-index 4), so no z-index here can go under it
without dropping behind the article as well. While one is open -- on a phone it fills the screen --
the box is taken off instead; wikt.cite puts the class on while its dialogs are showing. */
body.wikt-dialog-open .dsb-queue-box {
display: none;
}
.dsb-queue-title {
margin-right: 20px;
font-size: 13px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-queue-close {
float: right;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-queue-close:hover,
.dsb-queue-close:focus {
color: var(--wikt-accent-deep);
}
/* Stands in for the list before anything is queued, saying what the save button is for. */
.dsb-queue-empty {
margin: 8px 0;
font-size: 12px;
line-height: 1.5;
color: var(--wikt-ink-3);
}
.dsb-queue-list {
margin: 8px 0;
padding-left: 18px;
max-height: 160px;
overflow-y: auto;
}
.dsb-queue-list li {
margin-bottom: 4px;
}
/* Drops one queued change. Dim until its line is hovered, so the list stays a list and not a row of
buttons. */
.dsb-queue-drop {
margin-left: 6px;
padding: 0 3px;
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
opacity: 0;
transition: opacity 0.12s ease, color 0.12s ease;
}
.dsb-queue-list li:hover .dsb-queue-drop,
.dsb-queue-drop:focus {
opacity: 1;
}
.dsb-queue-drop:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* No hover on touch, so the button is simply there. */
@media (hover: none), (pointer: coarse) {
.dsb-queue-drop {
opacity: 1;
}
}
.dsb-queue-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
}
.dsb-queue-save,
.dsb-queue-cancel {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.dsb-queue-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-queue-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-queue-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-queue-cancel {
background: transparent;
border: 1px solid var(--wikt-field-edge);
color: var(--wikt-ink-3);
}
.dsb-queue-cancel:hover {
border-color: var(--wikt-ink-3);
color: var(--wikt-ink);
}
.dsb-queue-status {
margin-top: 6px;
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-queue-status.dsb-status-ok { color: var(--wikt-ok); }
.dsb-queue-status.dsb-status-error { color: var(--wikt-danger); }
/* ── Minerva (mobile) ────────────────────────────────────────────────
Narrower viewport: keep the panel on screen and give every tap
target the form's 44px minimum. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-quick-row {
font-size: 13px;
margin-left: 20px;
}
.dsb-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
min-height: 40px;
/* >=16px stops iOS Safari zooming the page on focus. */
font-size: 16px;
}
.dsb-tagfield .dsb-input {
min-height: 32px;
}
.dsb-tag-remove {
padding: 0 6px;
}
.dsb-save,
.dsb-cancel,
.dsb-autolink,
.dsb-queue-save,
.dsb-queue-cancel {
min-height: 40px;
padding: 8px 16px;
}
.dsb-queue-box {
top: auto;
bottom: 8px;
right: 8px;
left: 8px;
width: auto;
}
}
/* ── Dark mode ───────────────────────────────────────────────────────
Same warm dark palette and same explicit-signal-only rule as the
form's stylesheet: no bare prefers-color-scheme fallback, since
legacy Vector sets no theme class and would go dark on its own. */
html.skin-theme-clientpref-night .dsb-save,
html.skin-nightmode .dsb-save,
html.skin-theme-clientpref-night .dsb-queue-save,
html.skin-nightmode .dsb-queue-save {
color: #14213a;
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .dsb-save,
html.skin-theme-clientpref-os .dsb-queue-save {
color: #14213a;
}
}
/* Rules the menu's three sections apart (see MENU_GROUPS). */
.dsb-more-menu .dsb-menu-group-start {
margin-top: 7px;
padding-top: 7px;
border-top: 1px solid var(--wikt-rule-soft);
}
/* Deleting a sense is destructive, so it reads in the danger colour -- but it shares the last section
with "Maneyeke din zêde bike" (both act on the sense itself), so it carries no rule of its own. */
.dsb-more-menu .dsb-field-delete {
color: var(--wikt-danger);
}
.dsb-more-menu .dsb-field-delete:hover,
.dsb-more-menu .dsb-field-delete:focus {
color: var(--wikt-danger);
text-decoration: underline;
}
/* A sense queued for deletion, struck through until the queue is saved or the change dropped. */
li.dsb-sense-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
li.dsb-sense-doomed .dsb-quick-row {
text-decoration: none;
}
/* The row a new sense gets before it is saved: numbered by the list it sits in, with its own box. */
.dsb-new-sense-row .dsb-new-sense {
margin: 4px 0 8px;
}
.dsb-new-sense-quote {
margin-top: 4px;
padding: 4px 6px;
border-radius: var(--wikt-r-field, 6px);
background: var(--wikt-code-bg);
font-family: monospace;
font-size: 12px;
overflow-wrap: anywhere;
}
/* The word lists a not-yet-saved sense carries, and the links that add them. */
/* Same row of actions a saved sense has, so the menu can overlay rather than push the row down. */
.dsb-new-sense-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 12px;
margin-top: 6px;
}
.dsb-new-sense-lists:not(:empty) {
margin-top: 8px;
}
/* A quotation or example queued for removal, marked where it sits like a doomed sense. */
.dsb-example-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
opacity: 0.75;
}
/* Queue confirmation for row actions that have no field box of their own. */
.dsb-quick-note {
display: block;
margin-top: 6px;
}
/* Dropped by anything that cannot live in a 520px column: a long etymology, and a word list wide
enough to have gone to two columns. */
.dsb-subsection-panel.dsb-panel-wide {
max-width: none;
}
/* Word-list groups: one "{{kol3}}" per block, told apart only when there is more than one. */
.dsb-wordgroups-many .dsb-wordgroup {
padding-top: 10px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-wordgroups-many .dsb-wordgroup:first-child {
padding-top: 0;
border-top: 0;
}
.dsb-group-remove {
color: var(--wikt-danger);
}
/* A sernav-headed group inside an unsaved-section preview. */
.dsb-preview-subheading {
margin-top: 6px;
font-weight: 600;
}
/* ── Werger box form ──
The {{werger-ser}} parameters. Reuses .dsb-input / .dsb-adv-label from the other panels; only the
note and the disclosure body need anything of their own. */
.dsb-werger-note {
margin: 0 0 10px;
color: var(--wikt-muted);
font-size: 12px;
line-height: 1.5;
}
.dsb-werger-main {
display: flex;
flex-direction: column;
gap: 10px;
}
/* The box is nested inside its label, so it is made a block to sit under the label text instead of
beside it -- inline, a short label like "id" ends up behind its own box. */
.dsb-werger-row .dsb-adv-label {
display: block;
margin-bottom: 0;
}
.dsb-werger-row .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* ── Îzaha maneyî ──
Opens under the Mane box; the two are one gloss, so it sits tight against it. */
.dsb-izah-wrap {
margin-top: 6px;
}
.dsb-izah-wrap .dsb-adv-label {
display: block;
}
.dsb-izah-wrap .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* A sense redrawn from the change that is queued for it, or a whole new sense waiting in the list's own
numbering. No badge and no frame: the point is that it reads the way it will read once saved. The tint
is the only sign, and the queue box is what actually says nothing is published yet. */
.dsb-sense-preview {
background: var(--wikt-accent-tint);
box-shadow: -3px 0 0 var(--wikt-accent-edge);
}
/* Kept in the tree so another gadget's form still has somewhere to open, but out of sight while the
preview above draws the same content. */
.dsb-preview-hidden {
display: none !important;
}
li.dsb-sense-preview {
display: list-item;
padding-left: 4px;
}
/* </nowiki> */
oe8k0kq4ujcqt8v1k5ijek9vsw4xg2p
6535000
6534997
2026-09-20T19:49:27Z
Wikihez
7344
DefSideBoxes: a queued new sense redraws itself and can be reopened
6535000
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.css]]
*/
/* ── Searchable select ───────────────────────────────────────────────
The native <select> is hidden inside the wrapper and still holds the value; the input filters the list. */
.dsb-ac-wrap {
position: relative;
display: block;
}
.dsb-ac-input {
width: 100%;
box-sizing: border-box;
}
.dsb-ac-results {
position: absolute;
z-index: 30;
top: 100%;
left: 0;
right: 0;
max-height: 260px;
margin: 2px 0 0;
padding: 4px;
overflow-y: auto;
list-style: none;
font-family: var(--wikt-sans);
font-size: 13px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
.dsb-ac-results li {
padding: 6px 8px;
border-radius: 6px;
cursor: pointer;
}
.dsb-ac-results li:hover,
.dsb-ac-results li.dsb-ac-active {
background: var(--wikt-accent-tint);
}
/* ── Etîmolojî editing surface ───────────────────────────────────────
A contenteditable line of wikitext: text is typed straight into it, template calls sit in it as chips. */
.dsb-tpl-surface {
min-height: 72px;
margin-top: 6px;
padding: 8px 10px;
font-family: var(--wikt-sans);
font-size: 14px;
line-height: 1.6;
color: var(--wikt-ink);
white-space: pre-wrap;
word-wrap: break-word;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-tpl-surface:focus {
outline: none;
border-color: var(--wikt-accent);
box-shadow: 0 0 0 2px var(--wikt-accent-tint);
}
/* The chips: visibly not prose, and atomic to the caret. */
/* Takes the call off the line without going through the parameter dialog -- on a phone there is no
reliable backspace onto a contenteditable="false" chip. */
.dsb-tpl-chip-remove {
position: relative;
margin-left: 5px;
padding: 0 2px;
font-family: var(--wikt-sans);
font-size: 13px;
line-height: 1;
color: var(--wikt-accent-deep);
opacity: 0.6;
cursor: pointer;
}
.dsb-tpl-chip-remove:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* The glyph stays small; the tap target grows invisibly around it. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-tpl-chip-remove {
opacity: 1;
}
.dsb-tpl-chip-remove::after {
content: "";
position: absolute;
inset: -10px -8px;
}
}
.dsb-tpl-chip {
display: inline-block;
margin: 0 1px;
padding: 1px 6px;
font-family: var(--wikt-mono);
font-size: 12.5px;
color: var(--wikt-accent-deep);
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
vertical-align: baseline;
background: var(--wikt-accent-tint);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
touch-action: manipulation;
cursor: pointer;
user-select: none;
}
.dsb-tpl-chip:hover,
.dsb-tpl-chip:focus {
background: var(--wikt-field-bg);
border-color: var(--wikt-accent);
outline: none;
}
/* ── Template parameter form ─────────────────────────────────────────
.dsb-tpl-name/-desc/-param-row/-help/-undocumented/-modifier-list and the deprecated-label override now
live in cite.css (wikt.cite's own stylesheet): buildTemplateParamForm() moved there so start.js's own
Etîmolojî/Çavkanî pickers can open the same dialog. */
/* One-line variant: label beside its box rather than above it, for a single field whose name would
otherwise read as a heading for everything below it (the word groups' "Sernav"). */
.dsb-param-row-inline > .dsb-adv-label {
display: flex;
gap: 8px;
align-items: center;
}
/* Sernav is optional and names the group; the word boxes under it are what the form is actually for.
Letting it stretch to the full row made the one thing you may leave blank the widest box on the
card, so it takes only the space its own content needs. */
.dsb-param-row-inline .dsb-input {
flex: 0 1 auto;
width: 12em;
max-width: 40%;
/* .dsb-input's own 16em floor would win over the width above and make this the widest box on the
card, which is the opposite of the point. */
min-width: 0;
max-width: 100%;
min-width: 0;
}
.dsb-gloss-raw {
font-size: 12px;
}
/* Disclosure arrow on "Hevmane û Bêhtir", flipped when the menu is open. */
.dsb-more-arrow {
font-size: 10px;
vertical-align: 1px;
}
/* The POS panel's follow-up questions (cinsiyet / lektur): label above its select, like the field boxes. */
.dsb-pos-param-row {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 8px;
}
/* ── "Guhartina pêşketî" (advanced editing) panel ──────────────────────
Revealed by the pencil toggle above: every per-sense action this gadget offers, queued together with one
"Lê zêde bike". A plain inline row, not a bordered/background card. */
.dsb-quick-row {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
box-sizing: border-box;
max-width: 520px;
margin: 4px 0 0 16px;
font-family: var(--wikt-sans);
font-size: 12px;
color: var(--wikt-ink-2);
}
.dsb-quick-link {
align-self: flex-start;
margin-bottom: 10px;
font-size: 12px;
color: var(--wikt-accent);
text-decoration: none;
white-space: nowrap;
cursor: pointer;
}
/* The links under a word list ("+ Komek lê zêde bike", "Wekî wîkîtext biguhêre") sit in the field wrap
rather than in a flex column, so they need their own spacing or they run together. */
.dsb-subsection-field > .dsb-quick-link {
display: inline-block;
margin-right: 14px;
}
.dsb-quick-link:hover,
.dsb-quick-link:focus {
color: var(--wikt-accent-deep);
text-decoration: underline;
}
/* The picker row: gloss + the two primary word groups as direct "+ <field>" links, plus a "Hevmane û Bêhtir" toggle
for the rest -- clicking a link opens its box above the buttons and hides the link; .dsb-box-remove un-hides it.
position: relative anchors .dsb-more-menu's overlay below (see there). */
.dsb-field-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 4px 14px;
}
.dsb-field-picker .dsb-quick-link {
margin-bottom: 0;
}
/* Under the buttons once a box is open: a little space so the links do not read as part of the buttons. */
.dsb-open-boxes:not(:empty) ~ .dsb-field-picker {
margin-top: 8px;
}
/* The "Hevmane û Bêhtir" dropdown: every non-primary field link (plus Nimûne/jêgirtin, which sits first) stacked as a
plain vertical list. Floats over the page as an overlay (positioned against .dsb-field-picker) instead of
pushing $openBoxes/the rest of the definition down when it opens. */
.dsb-more-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 10;
display: flex;
flex-direction: column;
align-items: flex-start;
width: max-content;
max-width: min(320px, calc(100vw - 40px));
gap: 6px;
margin: 2px 0 0 4px;
padding: 8px 12px;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-field);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
}
.dsb-more-menu .dsb-quick-link {
margin-bottom: 0;
}
/* Boxes the user has actually opened, stacked in the order they were added -- several can be open at once. */
.dsb-open-boxes {
display: flex;
flex-direction: column;
}
/* Nothing open: take it out of the flow so the links do not sit above an empty gap. */
.dsb-open-boxes:empty {
display: none;
}
/* One field (its label + input) within the panel -- always a currently-open, removable box. */
.dsb-adv-group {
position: relative;
margin-bottom: 14px;
padding-right: 20px;
}
.dsb-adv-label {
margin-bottom: 4px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
/* The example sentence beside a word list's name: same text the wizard shows, so it reads as the
quieter half of the label rather than a second heading. */
.dsb-adv-hint {
margin-left: 6px;
font-weight: 400;
font-style: italic;
color: var(--wikt-muted);
}
/* The shared image rows (util.js's buildImageListField): one label + box per line, like the parameter
rows above them. Three identical boxes twice over is unreadable without a break between them, so each
image is its own block with its own name. */
.dsb-resim-list {
margin: 0;
padding-left: 0;
}
.dsb-resim-item {
list-style: none;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-resim-item:first-child {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.dsb-resim-index {
margin-bottom: 6px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
.dsb-box-remove {
position: absolute;
top: 0;
right: 0;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-box-remove:hover,
.dsb-box-remove:focus {
color: var(--wikt-accent-deep);
}
.dsb-form-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.dsb-input {
box-sizing: border-box;
min-width: 16em;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
/* The native corner is replaced by util.js's grip (see wikt-edit.css), which drags vertically only and
remembers the height. */
.dsb-subsection-panel textarea.dsb-input,
.dsb-adv-group textarea.dsb-input {
resize: none;
}
.dsb-input::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
.dsb-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* ── Plain tag field (Etîket, Kategorî) ────────────────────────────────
Plain type-and-commit tags with no suggestion panel; unlike Hevmane/Dijmane/etc., these carry no modifiers. */
.dsb-tagfield {
box-sizing: border-box;
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
min-width: 16em;
padding: 4px 6px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.dsb-tagfield:focus-within {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
}
.dsb-tag {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
padding: 3px 6px 3px 10px;
}
.dsb-tag-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
.dsb-tag-remove:hover {
color: var(--wikt-danger);
}
.dsb-tagfield .dsb-input {
flex: 1 1 8em;
min-width: 8em;
border: 0;
padding: 3px 2px;
background: transparent;
box-shadow: none;
}
.dsb-tagfield .dsb-input:focus {
box-shadow: none;
}
.dsb-save,
.dsb-cancel,
.dsb-delete,
.dsb-autolink {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
/* One filled pill per row — the same rule the form's footer follows. */
.dsb-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-cancel {
background: transparent;
border: 1px solid transparent;
color: var(--wikt-ink-3);
}
.dsb-cancel:hover {
border-color: var(--wikt-field-edge);
color: var(--wikt-ink);
}
/* Removing a heading: plain like Betal bike, in the danger colour, pushed to the far end of the row. */
.dsb-delete {
margin-left: auto;
background: transparent;
border: 1px solid transparent;
color: var(--wikt-danger);
}
.dsb-delete:hover {
border-color: var(--wikt-danger);
}
/* The heading's name, above the section's own box. */
.dsb-heading-name-wrap {
margin-bottom: 10px;
}
/* A heading queued for removal, with everything it owns, until the queue is saved or the change dropped. */
.dsb-preview-removed {
opacity: 0.5;
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
/* An outline pill, distinct from both the filled .dsb-save and the plain-text .dsb-cancel -- a secondary
utility action, not the primary save nor a dismissal. */
.dsb-autolink {
background: transparent;
border: 1px solid var(--wikt-accent-edge);
color: var(--wikt-accent);
}
.dsb-autolink:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-autolink:disabled {
opacity: 0.55;
cursor: progress;
}
/* Status line. Color comes from .dsb-status-ok/.dsb-status-error (added by the .js on success/failure)
rather than an inline style, so these tokens (and dark mode) apply. */
.dsb-status {
font-family: var(--wikt-sans);
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-status-ok { color: var(--wikt-ok); }
.dsb-status-error { color: var(--wikt-danger); }
/* ── "+ Beşek lê zêde bike" ──────────────────────────────────────────
An accent-coloured action link, like the form's .wiktku-adv-toggle:
it opens something, so it is not muted grey. */
/* iOS Safari zooms the page in whenever a field it focuses has text under 16px, and everything here is
12-13px for density. On touch the form controls go up to the size that stops it; the pointer query
leaves the desktop layout alone. */
@media (pointer: coarse) {
.dsb-input,
.dsb-tag-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
font-size: 16px;
}
/* Full width, whatever the viewport reports: a phone asked to render the desktop site says ~980px and
would otherwise leave these at their desktop size. */
.dsb-subsection-panel .dsb-input,
.dsb-subsection-panel .dsb-subsection-select,
.dsb-convert-pos-panel .dsb-convert-pos-select {
width: 100%;
max-width: 100%;
}
/* Sernav is the exception: it names the group, so it stays visibly smaller than the boxes it heads.
The cap matters more than the em width -- at 16px, 12em came out the same size as a word box. */
.dsb-subsection-panel .dsb-param-row-inline .dsb-input {
width: 8em;
max-width: 40%;
}
}
/* The image rows reuse the inline row, but nothing here is optional the way a group's "Sernav" is:
a file name and a caption want the whole row. */
.dsb-resim-list .dsb-param-row-inline > .dsb-adv-label,
.dsb-resim-list .dsb-tpl-param-row > label {
display: flex;
gap: 8px;
align-items: center;
}
.dsb-resim-list .dsb-param-row-inline .dsb-input,
.dsb-resim-list .dsb-tpl-param-row .dsb-input {
flex: 1 1 auto;
width: auto;
max-width: 100%;
}
/* ── Subsection panel ────────────────────────────────────────────────
A sunken card rather than a grey utility box; fields stack so the
picker and its input are not competing for one narrow row. */
.dsb-subsection-select,
.dsb-convert-pos-select {
box-sizing: border-box;
width: 100%;
max-width: 100%;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-subsection-select:focus,
.dsb-convert-pos-select:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The template-insert picker sits above the Çavkanî textarea, not in place of it -- a little breathing
room so the two don't read as one control. */
.dsb-cavkani-picker {
margin-bottom: 6px;
}
.dsb-subsection-field {
min-width: 0;
}
.dsb-subsection-words,
.dsb-subsection-text {
box-sizing: border-box;
width: 100%;
min-width: 0;
}
.dsb-subsection-text {
line-height: 1.6;
resize: vertical;
}
/* ── Gloss field inside the advanced-editing panel ────────────────────
Same shape as .dsb-subsection-text. */
.dsb-edit-gloss {
width: 100%;
line-height: 1.6;
resize: vertical;
}
/* ── Live "not saved yet" previews ─────────────────────────────────────
Dropped into the rendered page at the change's own spot. Dashed edge + a small badge mark them as
pending, not real content; they disappear on "Betal bike" or once the real save lands. */
.dsb-preview-block,
.dsb-preview-inline {
margin: 8px 0;
padding: 8px 12px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-accent-tint);
border: 1px dashed var(--wikt-accent-edge);
border-radius: var(--wikt-r-field);
}
.dsb-preview-inline {
margin-left: 26px;
max-width: 520px;
}
.dsb-preview-heading {
margin: 0 0 4px;
font-size: 15px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-preview-badge {
display: inline-block;
margin-bottom: 6px;
padding: 1px 8px;
font-size: 11px;
font-weight: 500;
color: var(--wikt-accent-deep);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
}
/* Parser output goes in here, so it drops the panel's own font and colour and takes the page's \u2014 the
point of the box is that what is inside it looks exactly like what will be saved. */
.dsb-preview-rendered,
.dsb-preview-rendered-inline {
font-family: inherit;
font-size: 14px;
color: var(--wikt-ink);
}
.dsb-preview-rendered-inline {
display: inline;
}
.dsb-preview-rendered-inline .mw-parser-output,
.dsb-preview-rendered-inline .mw-parser-output > p {
display: inline;
}
.dsb-preview-list,
.dsb-preview-text {
margin: 0;
padding-left: 18px;
}
.dsb-preview-text {
padding-left: 0;
}
/* ── Floating save-queue widget ─────────────────────────────────────
This gadget's own small floating box. One "Lê zêde bike" click anywhere on the page adds a line here;
"Guhertinan qeyd bike" makes the one real save; "Betal bike" drops everything queued without touching the page. */
.dsb-queue-box {
position: fixed;
top: 60px;
right: 16px;
z-index: 1000;
box-sizing: border-box;
width: 280px;
max-width: calc(100vw - 32px);
max-height: calc(100vh - 90px);
overflow-y: auto;
padding: 12px 14px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent);
border-radius: var(--wikt-r-card);
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.16);
}
/* An OOUI dialog sits low in the page's stacking order (z-index 4), so no z-index here can go under it
without dropping behind the article as well. While one is open -- on a phone it fills the screen --
the box is taken off instead; wikt.cite puts the class on while its dialogs are showing. */
body.wikt-dialog-open .dsb-queue-box {
display: none;
}
.dsb-queue-title {
margin-right: 20px;
font-size: 13px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-queue-close {
float: right;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-queue-close:hover,
.dsb-queue-close:focus {
color: var(--wikt-accent-deep);
}
/* Stands in for the list before anything is queued, saying what the save button is for. */
.dsb-queue-empty {
margin: 8px 0;
font-size: 12px;
line-height: 1.5;
color: var(--wikt-ink-3);
}
.dsb-queue-list {
margin: 8px 0;
padding-left: 18px;
max-height: 160px;
overflow-y: auto;
}
.dsb-queue-list li {
margin-bottom: 4px;
}
/* Drops one queued change. Dim until its line is hovered, so the list stays a list and not a row of
buttons. */
.dsb-queue-drop {
margin-left: 6px;
padding: 0 3px;
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
opacity: 0;
transition: opacity 0.12s ease, color 0.12s ease;
}
.dsb-queue-list li:hover .dsb-queue-drop,
.dsb-queue-drop:focus {
opacity: 1;
}
.dsb-queue-drop:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* No hover on touch, so the button is simply there. */
@media (hover: none), (pointer: coarse) {
.dsb-queue-drop {
opacity: 1;
}
}
.dsb-queue-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
}
.dsb-queue-save,
.dsb-queue-cancel {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.dsb-queue-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-queue-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-queue-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-queue-cancel {
background: transparent;
border: 1px solid var(--wikt-field-edge);
color: var(--wikt-ink-3);
}
.dsb-queue-cancel:hover {
border-color: var(--wikt-ink-3);
color: var(--wikt-ink);
}
.dsb-queue-status {
margin-top: 6px;
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-queue-status.dsb-status-ok { color: var(--wikt-ok); }
.dsb-queue-status.dsb-status-error { color: var(--wikt-danger); }
/* ── Minerva (mobile) ────────────────────────────────────────────────
Narrower viewport: keep the panel on screen and give every tap
target the form's 44px minimum. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-quick-row {
font-size: 13px;
margin-left: 20px;
}
.dsb-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
min-height: 40px;
/* >=16px stops iOS Safari zooming the page on focus. */
font-size: 16px;
}
.dsb-tagfield .dsb-input {
min-height: 32px;
}
.dsb-tag-remove {
padding: 0 6px;
}
.dsb-save,
.dsb-cancel,
.dsb-autolink,
.dsb-queue-save,
.dsb-queue-cancel {
min-height: 40px;
padding: 8px 16px;
}
.dsb-queue-box {
top: auto;
bottom: 8px;
right: 8px;
left: 8px;
width: auto;
}
}
/* ── Dark mode ───────────────────────────────────────────────────────
Same warm dark palette and same explicit-signal-only rule as the
form's stylesheet: no bare prefers-color-scheme fallback, since
legacy Vector sets no theme class and would go dark on its own. */
html.skin-theme-clientpref-night .dsb-save,
html.skin-nightmode .dsb-save,
html.skin-theme-clientpref-night .dsb-queue-save,
html.skin-nightmode .dsb-queue-save {
color: #14213a;
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .dsb-save,
html.skin-theme-clientpref-os .dsb-queue-save {
color: #14213a;
}
}
/* Rules the menu's three sections apart (see MENU_GROUPS). */
.dsb-more-menu .dsb-menu-group-start {
margin-top: 7px;
padding-top: 7px;
border-top: 1px solid var(--wikt-rule-soft);
}
/* Deleting a sense is destructive, so it reads in the danger colour -- but it shares the last section
with "Maneyeke din zêde bike" (both act on the sense itself), so it carries no rule of its own. */
.dsb-more-menu .dsb-field-delete {
color: var(--wikt-danger);
}
.dsb-more-menu .dsb-field-delete:hover,
.dsb-more-menu .dsb-field-delete:focus {
color: var(--wikt-danger);
text-decoration: underline;
}
/* A sense queued for deletion, struck through until the queue is saved or the change dropped. */
li.dsb-sense-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
li.dsb-sense-doomed .dsb-quick-row {
text-decoration: none;
}
/* The row a new sense gets before it is saved: numbered by the list it sits in, with its own box. */
.dsb-new-sense-row .dsb-new-sense {
margin: 4px 0 8px;
}
/* "Vê maneyê biguhêre" under a sense that is queued but not saved. */
.dsb-new-sense-actions {
margin-top: 4px;
}
.dsb-new-sense-quote {
margin-top: 4px;
padding: 4px 6px;
border-radius: var(--wikt-r-field, 6px);
background: var(--wikt-code-bg);
font-family: monospace;
font-size: 12px;
overflow-wrap: anywhere;
}
/* The word lists a not-yet-saved sense carries, and the links that add them. */
/* Same row of actions a saved sense has, so the menu can overlay rather than push the row down. */
.dsb-new-sense-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 12px;
margin-top: 6px;
}
.dsb-new-sense-lists:not(:empty) {
margin-top: 8px;
}
/* A quotation or example queued for removal, marked where it sits like a doomed sense. */
.dsb-example-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
opacity: 0.75;
}
/* Queue confirmation for row actions that have no field box of their own. */
.dsb-quick-note {
display: block;
margin-top: 6px;
}
/* Dropped by anything that cannot live in a 520px column: a long etymology, and a word list wide
enough to have gone to two columns. */
.dsb-subsection-panel.dsb-panel-wide {
max-width: none;
}
/* Word-list groups: one "{{kol3}}" per block, told apart only when there is more than one. */
.dsb-wordgroups-many .dsb-wordgroup {
padding-top: 10px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-wordgroups-many .dsb-wordgroup:first-child {
padding-top: 0;
border-top: 0;
}
.dsb-group-remove {
color: var(--wikt-danger);
}
/* A sernav-headed group inside an unsaved-section preview. */
.dsb-preview-subheading {
margin-top: 6px;
font-weight: 600;
}
/* ── Werger box form ──
The {{werger-ser}} parameters. Reuses .dsb-input / .dsb-adv-label from the other panels; only the
note and the disclosure body need anything of their own. */
.dsb-werger-note {
margin: 0 0 10px;
color: var(--wikt-muted);
font-size: 12px;
line-height: 1.5;
}
.dsb-werger-main {
display: flex;
flex-direction: column;
gap: 10px;
}
/* The box is nested inside its label, so it is made a block to sit under the label text instead of
beside it -- inline, a short label like "id" ends up behind its own box. */
.dsb-werger-row .dsb-adv-label {
display: block;
margin-bottom: 0;
}
.dsb-werger-row .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* ── Îzaha maneyî ──
Opens under the Mane box; the two are one gloss, so it sits tight against it. */
.dsb-izah-wrap {
margin-top: 6px;
}
.dsb-izah-wrap .dsb-adv-label {
display: block;
}
.dsb-izah-wrap .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* A sense redrawn from the change that is queued for it, or a whole new sense waiting in the list's own
numbering. No badge and no frame: the point is that it reads the way it will read once saved. The tint
is the only sign, and the queue box is what actually says nothing is published yet. */
.dsb-sense-preview {
background: var(--wikt-accent-tint);
box-shadow: -3px 0 0 var(--wikt-accent-edge);
}
/* Kept in the tree so another gadget's form still has somewhere to open, but out of sight while the
preview above draws the same content. */
.dsb-preview-hidden {
display: none !important;
}
li.dsb-sense-preview {
display: list-item;
padding-left: 4px;
}
/* </nowiki> */
ek2fx0lbtb8mij8i8wx48z8yx2tigfe
6535013
6535000
2026-09-20T20:13:11Z
Wikihez
7344
DefSideBoxes: the pending sense renders in page context, keeps its pencil visible, and the example form opens beside it
6535013
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.css]]
*/
/* ── Searchable select ───────────────────────────────────────────────
The native <select> is hidden inside the wrapper and still holds the value; the input filters the list. */
.dsb-ac-wrap {
position: relative;
display: block;
}
.dsb-ac-input {
width: 100%;
box-sizing: border-box;
}
.dsb-ac-results {
position: absolute;
z-index: 30;
top: 100%;
left: 0;
right: 0;
max-height: 260px;
margin: 2px 0 0;
padding: 4px;
overflow-y: auto;
list-style: none;
font-family: var(--wikt-sans);
font-size: 13px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
.dsb-ac-results li {
padding: 6px 8px;
border-radius: 6px;
cursor: pointer;
}
.dsb-ac-results li:hover,
.dsb-ac-results li.dsb-ac-active {
background: var(--wikt-accent-tint);
}
/* ── Etîmolojî editing surface ───────────────────────────────────────
A contenteditable line of wikitext: text is typed straight into it, template calls sit in it as chips. */
.dsb-tpl-surface {
min-height: 72px;
margin-top: 6px;
padding: 8px 10px;
font-family: var(--wikt-sans);
font-size: 14px;
line-height: 1.6;
color: var(--wikt-ink);
white-space: pre-wrap;
word-wrap: break-word;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-tpl-surface:focus {
outline: none;
border-color: var(--wikt-accent);
box-shadow: 0 0 0 2px var(--wikt-accent-tint);
}
/* The chips: visibly not prose, and atomic to the caret. */
/* Takes the call off the line without going through the parameter dialog -- on a phone there is no
reliable backspace onto a contenteditable="false" chip. */
.dsb-tpl-chip-remove {
position: relative;
margin-left: 5px;
padding: 0 2px;
font-family: var(--wikt-sans);
font-size: 13px;
line-height: 1;
color: var(--wikt-accent-deep);
opacity: 0.6;
cursor: pointer;
}
.dsb-tpl-chip-remove:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* The glyph stays small; the tap target grows invisibly around it. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-tpl-chip-remove {
opacity: 1;
}
.dsb-tpl-chip-remove::after {
content: "";
position: absolute;
inset: -10px -8px;
}
}
.dsb-tpl-chip {
display: inline-block;
margin: 0 1px;
padding: 1px 6px;
font-family: var(--wikt-mono);
font-size: 12.5px;
color: var(--wikt-accent-deep);
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
vertical-align: baseline;
background: var(--wikt-accent-tint);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
touch-action: manipulation;
cursor: pointer;
user-select: none;
}
.dsb-tpl-chip:hover,
.dsb-tpl-chip:focus {
background: var(--wikt-field-bg);
border-color: var(--wikt-accent);
outline: none;
}
/* ── Template parameter form ─────────────────────────────────────────
.dsb-tpl-name/-desc/-param-row/-help/-undocumented/-modifier-list and the deprecated-label override now
live in cite.css (wikt.cite's own stylesheet): buildTemplateParamForm() moved there so start.js's own
Etîmolojî/Çavkanî pickers can open the same dialog. */
/* One-line variant: label beside its box rather than above it, for a single field whose name would
otherwise read as a heading for everything below it (the word groups' "Sernav"). */
.dsb-param-row-inline > .dsb-adv-label {
display: flex;
gap: 8px;
align-items: center;
}
/* Sernav is optional and names the group; the word boxes under it are what the form is actually for.
Letting it stretch to the full row made the one thing you may leave blank the widest box on the
card, so it takes only the space its own content needs. */
.dsb-param-row-inline .dsb-input {
flex: 0 1 auto;
width: 12em;
max-width: 40%;
/* .dsb-input's own 16em floor would win over the width above and make this the widest box on the
card, which is the opposite of the point. */
min-width: 0;
max-width: 100%;
min-width: 0;
}
.dsb-gloss-raw {
font-size: 12px;
}
/* Disclosure arrow on "Hevmane û Bêhtir", flipped when the menu is open. */
.dsb-more-arrow {
font-size: 10px;
vertical-align: 1px;
}
/* The POS panel's follow-up questions (cinsiyet / lektur): label above its select, like the field boxes. */
.dsb-pos-param-row {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 8px;
}
/* ── "Guhartina pêşketî" (advanced editing) panel ──────────────────────
Revealed by the pencil toggle above: every per-sense action this gadget offers, queued together with one
"Lê zêde bike". A plain inline row, not a bordered/background card. */
.dsb-quick-row {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
box-sizing: border-box;
max-width: 520px;
margin: 4px 0 0 16px;
font-family: var(--wikt-sans);
font-size: 12px;
color: var(--wikt-ink-2);
}
.dsb-quick-link {
align-self: flex-start;
margin-bottom: 10px;
font-size: 12px;
color: var(--wikt-accent);
text-decoration: none;
white-space: nowrap;
cursor: pointer;
}
/* The links under a word list ("+ Komek lê zêde bike", "Wekî wîkîtext biguhêre") sit in the field wrap
rather than in a flex column, so they need their own spacing or they run together. */
.dsb-subsection-field > .dsb-quick-link {
display: inline-block;
margin-right: 14px;
}
.dsb-quick-link:hover,
.dsb-quick-link:focus {
color: var(--wikt-accent-deep);
text-decoration: underline;
}
/* The picker row: gloss + the two primary word groups as direct "+ <field>" links, plus a "Hevmane û Bêhtir" toggle
for the rest -- clicking a link opens its box above the buttons and hides the link; .dsb-box-remove un-hides it.
position: relative anchors .dsb-more-menu's overlay below (see there). */
.dsb-field-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 4px 14px;
}
.dsb-field-picker .dsb-quick-link {
margin-bottom: 0;
}
/* Under the buttons once a box is open: a little space so the links do not read as part of the buttons. */
.dsb-open-boxes:not(:empty) ~ .dsb-field-picker {
margin-top: 8px;
}
/* The "Hevmane û Bêhtir" dropdown: every non-primary field link (plus Nimûne/jêgirtin, which sits first) stacked as a
plain vertical list. Floats over the page as an overlay (positioned against .dsb-field-picker) instead of
pushing $openBoxes/the rest of the definition down when it opens. */
.dsb-more-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 10;
display: flex;
flex-direction: column;
align-items: flex-start;
width: max-content;
max-width: min(320px, calc(100vw - 40px));
gap: 6px;
margin: 2px 0 0 4px;
padding: 8px 12px;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-field);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
}
.dsb-more-menu .dsb-quick-link {
margin-bottom: 0;
}
/* Boxes the user has actually opened, stacked in the order they were added -- several can be open at once. */
.dsb-open-boxes {
display: flex;
flex-direction: column;
}
/* Nothing open: take it out of the flow so the links do not sit above an empty gap. */
.dsb-open-boxes:empty {
display: none;
}
/* One field (its label + input) within the panel -- always a currently-open, removable box. */
.dsb-adv-group {
position: relative;
margin-bottom: 14px;
padding-right: 20px;
}
.dsb-adv-label {
margin-bottom: 4px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
/* The example sentence beside a word list's name: same text the wizard shows, so it reads as the
quieter half of the label rather than a second heading. */
.dsb-adv-hint {
margin-left: 6px;
font-weight: 400;
font-style: italic;
color: var(--wikt-muted);
}
/* The shared image rows (util.js's buildImageListField): one label + box per line, like the parameter
rows above them. Three identical boxes twice over is unreadable without a break between them, so each
image is its own block with its own name. */
.dsb-resim-list {
margin: 0;
padding-left: 0;
}
.dsb-resim-item {
list-style: none;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-resim-item:first-child {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.dsb-resim-index {
margin-bottom: 6px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
.dsb-box-remove {
position: absolute;
top: 0;
right: 0;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-box-remove:hover,
.dsb-box-remove:focus {
color: var(--wikt-accent-deep);
}
.dsb-form-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.dsb-input {
box-sizing: border-box;
min-width: 16em;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
/* The native corner is replaced by util.js's grip (see wikt-edit.css), which drags vertically only and
remembers the height. */
.dsb-subsection-panel textarea.dsb-input,
.dsb-adv-group textarea.dsb-input {
resize: none;
}
.dsb-input::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
.dsb-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* ── Plain tag field (Etîket, Kategorî) ────────────────────────────────
Plain type-and-commit tags with no suggestion panel; unlike Hevmane/Dijmane/etc., these carry no modifiers. */
.dsb-tagfield {
box-sizing: border-box;
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
min-width: 16em;
padding: 4px 6px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.dsb-tagfield:focus-within {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
}
.dsb-tag {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
padding: 3px 6px 3px 10px;
}
.dsb-tag-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
.dsb-tag-remove:hover {
color: var(--wikt-danger);
}
.dsb-tagfield .dsb-input {
flex: 1 1 8em;
min-width: 8em;
border: 0;
padding: 3px 2px;
background: transparent;
box-shadow: none;
}
.dsb-tagfield .dsb-input:focus {
box-shadow: none;
}
.dsb-save,
.dsb-cancel,
.dsb-delete,
.dsb-autolink {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
/* One filled pill per row — the same rule the form's footer follows. */
.dsb-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-cancel {
background: transparent;
border: 1px solid transparent;
color: var(--wikt-ink-3);
}
.dsb-cancel:hover {
border-color: var(--wikt-field-edge);
color: var(--wikt-ink);
}
/* Removing a heading: plain like Betal bike, in the danger colour, pushed to the far end of the row. */
.dsb-delete {
margin-left: auto;
background: transparent;
border: 1px solid transparent;
color: var(--wikt-danger);
}
.dsb-delete:hover {
border-color: var(--wikt-danger);
}
/* The heading's name, above the section's own box. */
.dsb-heading-name-wrap {
margin-bottom: 10px;
}
/* A heading queued for removal, with everything it owns, until the queue is saved or the change dropped. */
.dsb-preview-removed {
opacity: 0.5;
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
/* An outline pill, distinct from both the filled .dsb-save and the plain-text .dsb-cancel -- a secondary
utility action, not the primary save nor a dismissal. */
.dsb-autolink {
background: transparent;
border: 1px solid var(--wikt-accent-edge);
color: var(--wikt-accent);
}
.dsb-autolink:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-autolink:disabled {
opacity: 0.55;
cursor: progress;
}
/* Status line. Color comes from .dsb-status-ok/.dsb-status-error (added by the .js on success/failure)
rather than an inline style, so these tokens (and dark mode) apply. */
.dsb-status {
font-family: var(--wikt-sans);
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-status-ok { color: var(--wikt-ok); }
.dsb-status-error { color: var(--wikt-danger); }
/* ── "+ Beşek lê zêde bike" ──────────────────────────────────────────
An accent-coloured action link, like the form's .wiktku-adv-toggle:
it opens something, so it is not muted grey. */
/* iOS Safari zooms the page in whenever a field it focuses has text under 16px, and everything here is
12-13px for density. On touch the form controls go up to the size that stops it; the pointer query
leaves the desktop layout alone. */
@media (pointer: coarse) {
.dsb-input,
.dsb-tag-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
font-size: 16px;
}
/* Full width, whatever the viewport reports: a phone asked to render the desktop site says ~980px and
would otherwise leave these at their desktop size. */
.dsb-subsection-panel .dsb-input,
.dsb-subsection-panel .dsb-subsection-select,
.dsb-convert-pos-panel .dsb-convert-pos-select {
width: 100%;
max-width: 100%;
}
/* Sernav is the exception: it names the group, so it stays visibly smaller than the boxes it heads.
The cap matters more than the em width -- at 16px, 12em came out the same size as a word box. */
.dsb-subsection-panel .dsb-param-row-inline .dsb-input {
width: 8em;
max-width: 40%;
}
}
/* The image rows reuse the inline row, but nothing here is optional the way a group's "Sernav" is:
a file name and a caption want the whole row. */
.dsb-resim-list .dsb-param-row-inline > .dsb-adv-label,
.dsb-resim-list .dsb-tpl-param-row > label {
display: flex;
gap: 8px;
align-items: center;
}
.dsb-resim-list .dsb-param-row-inline .dsb-input,
.dsb-resim-list .dsb-tpl-param-row .dsb-input {
flex: 1 1 auto;
width: auto;
max-width: 100%;
}
/* ── Subsection panel ────────────────────────────────────────────────
A sunken card rather than a grey utility box; fields stack so the
picker and its input are not competing for one narrow row. */
.dsb-subsection-select,
.dsb-convert-pos-select {
box-sizing: border-box;
width: 100%;
max-width: 100%;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-subsection-select:focus,
.dsb-convert-pos-select:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The template-insert picker sits above the Çavkanî textarea, not in place of it -- a little breathing
room so the two don't read as one control. */
.dsb-cavkani-picker {
margin-bottom: 6px;
}
.dsb-subsection-field {
min-width: 0;
}
.dsb-subsection-words,
.dsb-subsection-text {
box-sizing: border-box;
width: 100%;
min-width: 0;
}
.dsb-subsection-text {
line-height: 1.6;
resize: vertical;
}
/* ── Gloss field inside the advanced-editing panel ────────────────────
Same shape as .dsb-subsection-text. */
.dsb-edit-gloss {
width: 100%;
line-height: 1.6;
resize: vertical;
}
/* ── Live "not saved yet" previews ─────────────────────────────────────
Dropped into the rendered page at the change's own spot. Dashed edge + a small badge mark them as
pending, not real content; they disappear on "Betal bike" or once the real save lands. */
.dsb-preview-block,
.dsb-preview-inline {
margin: 8px 0;
padding: 8px 12px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-accent-tint);
border: 1px dashed var(--wikt-accent-edge);
border-radius: var(--wikt-r-field);
}
.dsb-preview-inline {
margin-left: 26px;
max-width: 520px;
}
.dsb-preview-heading {
margin: 0 0 4px;
font-size: 15px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-preview-badge {
display: inline-block;
margin-bottom: 6px;
padding: 1px 8px;
font-size: 11px;
font-weight: 500;
color: var(--wikt-accent-deep);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
}
/* Parser output goes in here, so it drops the panel's own font and colour and takes the page's \u2014 the
point of the box is that what is inside it looks exactly like what will be saved. */
.dsb-preview-rendered,
.dsb-preview-rendered-inline {
font-family: inherit;
font-size: 14px;
color: var(--wikt-ink);
}
.dsb-preview-rendered-inline {
display: inline;
}
.dsb-preview-rendered-inline .mw-parser-output,
.dsb-preview-rendered-inline .mw-parser-output > p {
display: inline;
}
.dsb-preview-list,
.dsb-preview-text {
margin: 0;
padding-left: 18px;
}
.dsb-preview-text {
padding-left: 0;
}
/* ── Floating save-queue widget ─────────────────────────────────────
This gadget's own small floating box. One "Lê zêde bike" click anywhere on the page adds a line here;
"Guhertinan qeyd bike" makes the one real save; "Betal bike" drops everything queued without touching the page. */
.dsb-queue-box {
position: fixed;
top: 60px;
right: 16px;
z-index: 1000;
box-sizing: border-box;
width: 280px;
max-width: calc(100vw - 32px);
max-height: calc(100vh - 90px);
overflow-y: auto;
padding: 12px 14px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent);
border-radius: var(--wikt-r-card);
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.16);
}
/* An OOUI dialog sits low in the page's stacking order (z-index 4), so no z-index here can go under it
without dropping behind the article as well. While one is open -- on a phone it fills the screen --
the box is taken off instead; wikt.cite puts the class on while its dialogs are showing. */
body.wikt-dialog-open .dsb-queue-box {
display: none;
}
.dsb-queue-title {
margin-right: 20px;
font-size: 13px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-queue-close {
float: right;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-queue-close:hover,
.dsb-queue-close:focus {
color: var(--wikt-accent-deep);
}
/* Stands in for the list before anything is queued, saying what the save button is for. */
.dsb-queue-empty {
margin: 8px 0;
font-size: 12px;
line-height: 1.5;
color: var(--wikt-ink-3);
}
.dsb-queue-list {
margin: 8px 0;
padding-left: 18px;
max-height: 160px;
overflow-y: auto;
}
.dsb-queue-list li {
margin-bottom: 4px;
}
/* Drops one queued change. Dim until its line is hovered, so the list stays a list and not a row of
buttons. */
.dsb-queue-drop {
margin-left: 6px;
padding: 0 3px;
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
opacity: 0;
transition: opacity 0.12s ease, color 0.12s ease;
}
.dsb-queue-list li:hover .dsb-queue-drop,
.dsb-queue-drop:focus {
opacity: 1;
}
.dsb-queue-drop:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* No hover on touch, so the button is simply there. */
@media (hover: none), (pointer: coarse) {
.dsb-queue-drop {
opacity: 1;
}
}
.dsb-queue-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
}
.dsb-queue-save,
.dsb-queue-cancel {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.dsb-queue-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-queue-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-queue-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-queue-cancel {
background: transparent;
border: 1px solid var(--wikt-field-edge);
color: var(--wikt-ink-3);
}
.dsb-queue-cancel:hover {
border-color: var(--wikt-ink-3);
color: var(--wikt-ink);
}
.dsb-queue-status {
margin-top: 6px;
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-queue-status.dsb-status-ok { color: var(--wikt-ok); }
.dsb-queue-status.dsb-status-error { color: var(--wikt-danger); }
/* ── Minerva (mobile) ────────────────────────────────────────────────
Narrower viewport: keep the panel on screen and give every tap
target the form's 44px minimum. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-quick-row {
font-size: 13px;
margin-left: 20px;
}
.dsb-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
min-height: 40px;
/* >=16px stops iOS Safari zooming the page on focus. */
font-size: 16px;
}
.dsb-tagfield .dsb-input {
min-height: 32px;
}
.dsb-tag-remove {
padding: 0 6px;
}
.dsb-save,
.dsb-cancel,
.dsb-autolink,
.dsb-queue-save,
.dsb-queue-cancel {
min-height: 40px;
padding: 8px 16px;
}
.dsb-queue-box {
top: auto;
bottom: 8px;
right: 8px;
left: 8px;
width: auto;
}
}
/* ── Dark mode ───────────────────────────────────────────────────────
Same warm dark palette and same explicit-signal-only rule as the
form's stylesheet: no bare prefers-color-scheme fallback, since
legacy Vector sets no theme class and would go dark on its own. */
html.skin-theme-clientpref-night .dsb-save,
html.skin-nightmode .dsb-save,
html.skin-theme-clientpref-night .dsb-queue-save,
html.skin-nightmode .dsb-queue-save {
color: #14213a;
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .dsb-save,
html.skin-theme-clientpref-os .dsb-queue-save {
color: #14213a;
}
}
/* Rules the menu's three sections apart (see MENU_GROUPS). */
.dsb-more-menu .dsb-menu-group-start {
margin-top: 7px;
padding-top: 7px;
border-top: 1px solid var(--wikt-rule-soft);
}
/* Deleting a sense is destructive, so it reads in the danger colour -- but it shares the last section
with "Maneyeke din zêde bike" (both act on the sense itself), so it carries no rule of its own. */
.dsb-more-menu .dsb-field-delete {
color: var(--wikt-danger);
}
.dsb-more-menu .dsb-field-delete:hover,
.dsb-more-menu .dsb-field-delete:focus {
color: var(--wikt-danger);
text-decoration: underline;
}
/* A sense queued for deletion, struck through until the queue is saved or the change dropped. */
li.dsb-sense-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
li.dsb-sense-doomed .dsb-quick-row {
text-decoration: none;
}
/* The row a new sense gets before it is saved: numbered by the list it sits in, with its own box. */
.dsb-new-sense-row .dsb-new-sense {
margin: 4px 0 8px;
}
/* The pencil on a pending sense stays visible: elsewhere it appears on hover, but here it is the only
way back into the box, and the row is a preview rather than page content. */
.dsb-new-sense-pending .dsb-example-toolbar .dsb-toggle {
opacity: 1;
}
.dsb-new-sense-quote {
margin-top: 4px;
padding: 4px 6px;
border-radius: var(--wikt-r-field, 6px);
background: var(--wikt-code-bg);
font-family: monospace;
font-size: 12px;
overflow-wrap: anywhere;
}
/* The word lists a not-yet-saved sense carries, and the links that add them. */
/* Same row of actions a saved sense has, so the menu can overlay rather than push the row down. */
.dsb-new-sense-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 12px;
margin-top: 6px;
}
.dsb-new-sense-lists:not(:empty) {
margin-top: 8px;
}
/* A quotation or example queued for removal, marked where it sits like a doomed sense. */
.dsb-example-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
opacity: 0.75;
}
/* Queue confirmation for row actions that have no field box of their own. */
.dsb-quick-note {
display: block;
margin-top: 6px;
}
/* Dropped by anything that cannot live in a 520px column: a long etymology, and a word list wide
enough to have gone to two columns. */
.dsb-subsection-panel.dsb-panel-wide {
max-width: none;
}
/* Word-list groups: one "{{kol3}}" per block, told apart only when there is more than one. */
.dsb-wordgroups-many .dsb-wordgroup {
padding-top: 10px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-wordgroups-many .dsb-wordgroup:first-child {
padding-top: 0;
border-top: 0;
}
.dsb-group-remove {
color: var(--wikt-danger);
}
/* A sernav-headed group inside an unsaved-section preview. */
.dsb-preview-subheading {
margin-top: 6px;
font-weight: 600;
}
/* ── Werger box form ──
The {{werger-ser}} parameters. Reuses .dsb-input / .dsb-adv-label from the other panels; only the
note and the disclosure body need anything of their own. */
.dsb-werger-note {
margin: 0 0 10px;
color: var(--wikt-muted);
font-size: 12px;
line-height: 1.5;
}
.dsb-werger-main {
display: flex;
flex-direction: column;
gap: 10px;
}
/* The box is nested inside its label, so it is made a block to sit under the label text instead of
beside it -- inline, a short label like "id" ends up behind its own box. */
.dsb-werger-row .dsb-adv-label {
display: block;
margin-bottom: 0;
}
.dsb-werger-row .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* ── Îzaha maneyî ──
Opens under the Mane box; the two are one gloss, so it sits tight against it. */
.dsb-izah-wrap {
margin-top: 6px;
}
.dsb-izah-wrap .dsb-adv-label {
display: block;
}
.dsb-izah-wrap .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* A sense redrawn from the change that is queued for it, or a whole new sense waiting in the list's own
numbering. No badge and no frame: the point is that it reads the way it will read once saved. The tint
is the only sign, and the queue box is what actually says nothing is published yet. */
.dsb-sense-preview {
background: var(--wikt-accent-tint);
box-shadow: -3px 0 0 var(--wikt-accent-edge);
}
/* Kept in the tree so another gadget's form still has somewhere to open, but out of sight while the
preview above draws the same content. */
.dsb-preview-hidden {
display: none !important;
}
li.dsb-sense-preview {
display: list-item;
padding-left: 4px;
}
/* </nowiki> */
daeytfwa1j95zk7v9d8w7i8lodgccdx
6535015
6535013
2026-09-20T20:22:15Z
Wikihez
7344
DefSideBoxes: the new-sense row gets every field, an inline pencil, and cancel that only closes
6535015
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.css]]
*/
/* ── Searchable select ───────────────────────────────────────────────
The native <select> is hidden inside the wrapper and still holds the value; the input filters the list. */
.dsb-ac-wrap {
position: relative;
display: block;
}
.dsb-ac-input {
width: 100%;
box-sizing: border-box;
}
.dsb-ac-results {
position: absolute;
z-index: 30;
top: 100%;
left: 0;
right: 0;
max-height: 260px;
margin: 2px 0 0;
padding: 4px;
overflow-y: auto;
list-style: none;
font-family: var(--wikt-sans);
font-size: 13px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
.dsb-ac-results li {
padding: 6px 8px;
border-radius: 6px;
cursor: pointer;
}
.dsb-ac-results li:hover,
.dsb-ac-results li.dsb-ac-active {
background: var(--wikt-accent-tint);
}
/* ── Etîmolojî editing surface ───────────────────────────────────────
A contenteditable line of wikitext: text is typed straight into it, template calls sit in it as chips. */
.dsb-tpl-surface {
min-height: 72px;
margin-top: 6px;
padding: 8px 10px;
font-family: var(--wikt-sans);
font-size: 14px;
line-height: 1.6;
color: var(--wikt-ink);
white-space: pre-wrap;
word-wrap: break-word;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-tpl-surface:focus {
outline: none;
border-color: var(--wikt-accent);
box-shadow: 0 0 0 2px var(--wikt-accent-tint);
}
/* The chips: visibly not prose, and atomic to the caret. */
/* Takes the call off the line without going through the parameter dialog -- on a phone there is no
reliable backspace onto a contenteditable="false" chip. */
.dsb-tpl-chip-remove {
position: relative;
margin-left: 5px;
padding: 0 2px;
font-family: var(--wikt-sans);
font-size: 13px;
line-height: 1;
color: var(--wikt-accent-deep);
opacity: 0.6;
cursor: pointer;
}
.dsb-tpl-chip-remove:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* The glyph stays small; the tap target grows invisibly around it. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-tpl-chip-remove {
opacity: 1;
}
.dsb-tpl-chip-remove::after {
content: "";
position: absolute;
inset: -10px -8px;
}
}
.dsb-tpl-chip {
display: inline-block;
margin: 0 1px;
padding: 1px 6px;
font-family: var(--wikt-mono);
font-size: 12.5px;
color: var(--wikt-accent-deep);
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
vertical-align: baseline;
background: var(--wikt-accent-tint);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
touch-action: manipulation;
cursor: pointer;
user-select: none;
}
.dsb-tpl-chip:hover,
.dsb-tpl-chip:focus {
background: var(--wikt-field-bg);
border-color: var(--wikt-accent);
outline: none;
}
/* ── Template parameter form ─────────────────────────────────────────
.dsb-tpl-name/-desc/-param-row/-help/-undocumented/-modifier-list and the deprecated-label override now
live in cite.css (wikt.cite's own stylesheet): buildTemplateParamForm() moved there so start.js's own
Etîmolojî/Çavkanî pickers can open the same dialog. */
/* One-line variant: label beside its box rather than above it, for a single field whose name would
otherwise read as a heading for everything below it (the word groups' "Sernav"). */
.dsb-param-row-inline > .dsb-adv-label {
display: flex;
gap: 8px;
align-items: center;
}
/* Sernav is optional and names the group; the word boxes under it are what the form is actually for.
Letting it stretch to the full row made the one thing you may leave blank the widest box on the
card, so it takes only the space its own content needs. */
.dsb-param-row-inline .dsb-input {
flex: 0 1 auto;
width: 12em;
max-width: 40%;
/* .dsb-input's own 16em floor would win over the width above and make this the widest box on the
card, which is the opposite of the point. */
min-width: 0;
max-width: 100%;
min-width: 0;
}
.dsb-gloss-raw {
font-size: 12px;
}
/* Disclosure arrow on "Hevmane û Bêhtir", flipped when the menu is open. */
.dsb-more-arrow {
font-size: 10px;
vertical-align: 1px;
}
/* The POS panel's follow-up questions (cinsiyet / lektur): label above its select, like the field boxes. */
.dsb-pos-param-row {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 8px;
}
/* ── "Guhartina pêşketî" (advanced editing) panel ──────────────────────
Revealed by the pencil toggle above: every per-sense action this gadget offers, queued together with one
"Lê zêde bike". A plain inline row, not a bordered/background card. */
.dsb-quick-row {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
box-sizing: border-box;
max-width: 520px;
margin: 4px 0 0 16px;
font-family: var(--wikt-sans);
font-size: 12px;
color: var(--wikt-ink-2);
}
.dsb-quick-link {
align-self: flex-start;
margin-bottom: 10px;
font-size: 12px;
color: var(--wikt-accent);
text-decoration: none;
white-space: nowrap;
cursor: pointer;
}
/* The links under a word list ("+ Komek lê zêde bike", "Wekî wîkîtext biguhêre") sit in the field wrap
rather than in a flex column, so they need their own spacing or they run together. */
.dsb-subsection-field > .dsb-quick-link {
display: inline-block;
margin-right: 14px;
}
.dsb-quick-link:hover,
.dsb-quick-link:focus {
color: var(--wikt-accent-deep);
text-decoration: underline;
}
/* The picker row: gloss + the two primary word groups as direct "+ <field>" links, plus a "Hevmane û Bêhtir" toggle
for the rest -- clicking a link opens its box above the buttons and hides the link; .dsb-box-remove un-hides it.
position: relative anchors .dsb-more-menu's overlay below (see there). */
.dsb-field-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 4px 14px;
}
.dsb-field-picker .dsb-quick-link {
margin-bottom: 0;
}
/* Under the buttons once a box is open: a little space so the links do not read as part of the buttons. */
.dsb-open-boxes:not(:empty) ~ .dsb-field-picker {
margin-top: 8px;
}
/* The "Hevmane û Bêhtir" dropdown: every non-primary field link (plus Nimûne/jêgirtin, which sits first) stacked as a
plain vertical list. Floats over the page as an overlay (positioned against .dsb-field-picker) instead of
pushing $openBoxes/the rest of the definition down when it opens. */
.dsb-more-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 10;
display: flex;
flex-direction: column;
align-items: flex-start;
width: max-content;
max-width: min(320px, calc(100vw - 40px));
gap: 6px;
margin: 2px 0 0 4px;
padding: 8px 12px;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-field);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
}
.dsb-more-menu .dsb-quick-link {
margin-bottom: 0;
}
/* Boxes the user has actually opened, stacked in the order they were added -- several can be open at once. */
.dsb-open-boxes {
display: flex;
flex-direction: column;
}
/* Nothing open: take it out of the flow so the links do not sit above an empty gap. */
.dsb-open-boxes:empty {
display: none;
}
/* One field (its label + input) within the panel -- always a currently-open, removable box. */
.dsb-adv-group {
position: relative;
margin-bottom: 14px;
padding-right: 20px;
}
.dsb-adv-label {
margin-bottom: 4px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
/* The example sentence beside a word list's name: same text the wizard shows, so it reads as the
quieter half of the label rather than a second heading. */
.dsb-adv-hint {
margin-left: 6px;
font-weight: 400;
font-style: italic;
color: var(--wikt-muted);
}
/* The shared image rows (util.js's buildImageListField): one label + box per line, like the parameter
rows above them. Three identical boxes twice over is unreadable without a break between them, so each
image is its own block with its own name. */
.dsb-resim-list {
margin: 0;
padding-left: 0;
}
.dsb-resim-item {
list-style: none;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-resim-item:first-child {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.dsb-resim-index {
margin-bottom: 6px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
.dsb-box-remove {
position: absolute;
top: 0;
right: 0;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-box-remove:hover,
.dsb-box-remove:focus {
color: var(--wikt-accent-deep);
}
.dsb-form-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.dsb-input {
box-sizing: border-box;
min-width: 16em;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
/* The native corner is replaced by util.js's grip (see wikt-edit.css), which drags vertically only and
remembers the height. */
.dsb-subsection-panel textarea.dsb-input,
.dsb-adv-group textarea.dsb-input {
resize: none;
}
.dsb-input::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
.dsb-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* ── Plain tag field (Etîket, Kategorî) ────────────────────────────────
Plain type-and-commit tags with no suggestion panel; unlike Hevmane/Dijmane/etc., these carry no modifiers. */
.dsb-tagfield {
box-sizing: border-box;
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
min-width: 16em;
padding: 4px 6px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.dsb-tagfield:focus-within {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
}
.dsb-tag {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
padding: 3px 6px 3px 10px;
}
.dsb-tag-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
.dsb-tag-remove:hover {
color: var(--wikt-danger);
}
.dsb-tagfield .dsb-input {
flex: 1 1 8em;
min-width: 8em;
border: 0;
padding: 3px 2px;
background: transparent;
box-shadow: none;
}
.dsb-tagfield .dsb-input:focus {
box-shadow: none;
}
.dsb-save,
.dsb-cancel,
.dsb-delete,
.dsb-autolink {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
/* One filled pill per row — the same rule the form's footer follows. */
.dsb-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-cancel {
background: transparent;
border: 1px solid transparent;
color: var(--wikt-ink-3);
}
.dsb-cancel:hover {
border-color: var(--wikt-field-edge);
color: var(--wikt-ink);
}
/* Removing a heading: plain like Betal bike, in the danger colour, pushed to the far end of the row. */
.dsb-delete {
margin-left: auto;
background: transparent;
border: 1px solid transparent;
color: var(--wikt-danger);
}
.dsb-delete:hover {
border-color: var(--wikt-danger);
}
/* The heading's name, above the section's own box. */
.dsb-heading-name-wrap {
margin-bottom: 10px;
}
/* A heading queued for removal, with everything it owns, until the queue is saved or the change dropped. */
.dsb-preview-removed {
opacity: 0.5;
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
/* An outline pill, distinct from both the filled .dsb-save and the plain-text .dsb-cancel -- a secondary
utility action, not the primary save nor a dismissal. */
.dsb-autolink {
background: transparent;
border: 1px solid var(--wikt-accent-edge);
color: var(--wikt-accent);
}
.dsb-autolink:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-autolink:disabled {
opacity: 0.55;
cursor: progress;
}
/* Status line. Color comes from .dsb-status-ok/.dsb-status-error (added by the .js on success/failure)
rather than an inline style, so these tokens (and dark mode) apply. */
.dsb-status {
font-family: var(--wikt-sans);
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-status-ok { color: var(--wikt-ok); }
.dsb-status-error { color: var(--wikt-danger); }
/* ── "+ Beşek lê zêde bike" ──────────────────────────────────────────
An accent-coloured action link, like the form's .wiktku-adv-toggle:
it opens something, so it is not muted grey. */
/* iOS Safari zooms the page in whenever a field it focuses has text under 16px, and everything here is
12-13px for density. On touch the form controls go up to the size that stops it; the pointer query
leaves the desktop layout alone. */
@media (pointer: coarse) {
.dsb-input,
.dsb-tag-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
font-size: 16px;
}
/* Full width, whatever the viewport reports: a phone asked to render the desktop site says ~980px and
would otherwise leave these at their desktop size. */
.dsb-subsection-panel .dsb-input,
.dsb-subsection-panel .dsb-subsection-select,
.dsb-convert-pos-panel .dsb-convert-pos-select {
width: 100%;
max-width: 100%;
}
/* Sernav is the exception: it names the group, so it stays visibly smaller than the boxes it heads.
The cap matters more than the em width -- at 16px, 12em came out the same size as a word box. */
.dsb-subsection-panel .dsb-param-row-inline .dsb-input {
width: 8em;
max-width: 40%;
}
}
/* The image rows reuse the inline row, but nothing here is optional the way a group's "Sernav" is:
a file name and a caption want the whole row. */
.dsb-resim-list .dsb-param-row-inline > .dsb-adv-label,
.dsb-resim-list .dsb-tpl-param-row > label {
display: flex;
gap: 8px;
align-items: center;
}
.dsb-resim-list .dsb-param-row-inline .dsb-input,
.dsb-resim-list .dsb-tpl-param-row .dsb-input {
flex: 1 1 auto;
width: auto;
max-width: 100%;
}
/* ── Subsection panel ────────────────────────────────────────────────
A sunken card rather than a grey utility box; fields stack so the
picker and its input are not competing for one narrow row. */
.dsb-subsection-select,
.dsb-convert-pos-select {
box-sizing: border-box;
width: 100%;
max-width: 100%;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-subsection-select:focus,
.dsb-convert-pos-select:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The template-insert picker sits above the Çavkanî textarea, not in place of it -- a little breathing
room so the two don't read as one control. */
.dsb-cavkani-picker {
margin-bottom: 6px;
}
.dsb-subsection-field {
min-width: 0;
}
.dsb-subsection-words,
.dsb-subsection-text {
box-sizing: border-box;
width: 100%;
min-width: 0;
}
.dsb-subsection-text {
line-height: 1.6;
resize: vertical;
}
/* ── Gloss field inside the advanced-editing panel ────────────────────
Same shape as .dsb-subsection-text. */
.dsb-edit-gloss {
width: 100%;
line-height: 1.6;
resize: vertical;
}
/* ── Live "not saved yet" previews ─────────────────────────────────────
Dropped into the rendered page at the change's own spot. Dashed edge + a small badge mark them as
pending, not real content; they disappear on "Betal bike" or once the real save lands. */
.dsb-preview-block,
.dsb-preview-inline {
margin: 8px 0;
padding: 8px 12px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-accent-tint);
border: 1px dashed var(--wikt-accent-edge);
border-radius: var(--wikt-r-field);
}
.dsb-preview-inline {
margin-left: 26px;
max-width: 520px;
}
.dsb-preview-heading {
margin: 0 0 4px;
font-size: 15px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-preview-badge {
display: inline-block;
margin-bottom: 6px;
padding: 1px 8px;
font-size: 11px;
font-weight: 500;
color: var(--wikt-accent-deep);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
}
/* Parser output goes in here, so it drops the panel's own font and colour and takes the page's \u2014 the
point of the box is that what is inside it looks exactly like what will be saved. */
.dsb-preview-rendered,
.dsb-preview-rendered-inline {
font-family: inherit;
font-size: 14px;
color: var(--wikt-ink);
}
.dsb-preview-rendered-inline {
display: inline;
}
.dsb-preview-rendered-inline .mw-parser-output,
.dsb-preview-rendered-inline .mw-parser-output > p {
display: inline;
}
.dsb-preview-list,
.dsb-preview-text {
margin: 0;
padding-left: 18px;
}
.dsb-preview-text {
padding-left: 0;
}
/* ── Floating save-queue widget ─────────────────────────────────────
This gadget's own small floating box. One "Lê zêde bike" click anywhere on the page adds a line here;
"Guhertinan qeyd bike" makes the one real save; "Betal bike" drops everything queued without touching the page. */
.dsb-queue-box {
position: fixed;
top: 60px;
right: 16px;
z-index: 1000;
box-sizing: border-box;
width: 280px;
max-width: calc(100vw - 32px);
max-height: calc(100vh - 90px);
overflow-y: auto;
padding: 12px 14px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent);
border-radius: var(--wikt-r-card);
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.16);
}
/* An OOUI dialog sits low in the page's stacking order (z-index 4), so no z-index here can go under it
without dropping behind the article as well. While one is open -- on a phone it fills the screen --
the box is taken off instead; wikt.cite puts the class on while its dialogs are showing. */
body.wikt-dialog-open .dsb-queue-box {
display: none;
}
.dsb-queue-title {
margin-right: 20px;
font-size: 13px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-queue-close {
float: right;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-queue-close:hover,
.dsb-queue-close:focus {
color: var(--wikt-accent-deep);
}
/* Stands in for the list before anything is queued, saying what the save button is for. */
.dsb-queue-empty {
margin: 8px 0;
font-size: 12px;
line-height: 1.5;
color: var(--wikt-ink-3);
}
.dsb-queue-list {
margin: 8px 0;
padding-left: 18px;
max-height: 160px;
overflow-y: auto;
}
.dsb-queue-list li {
margin-bottom: 4px;
}
/* Drops one queued change. Dim until its line is hovered, so the list stays a list and not a row of
buttons. */
.dsb-queue-drop {
margin-left: 6px;
padding: 0 3px;
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
opacity: 0;
transition: opacity 0.12s ease, color 0.12s ease;
}
.dsb-queue-list li:hover .dsb-queue-drop,
.dsb-queue-drop:focus {
opacity: 1;
}
.dsb-queue-drop:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* No hover on touch, so the button is simply there. */
@media (hover: none), (pointer: coarse) {
.dsb-queue-drop {
opacity: 1;
}
}
.dsb-queue-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
}
.dsb-queue-save,
.dsb-queue-cancel {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.dsb-queue-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-queue-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-queue-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-queue-cancel {
background: transparent;
border: 1px solid var(--wikt-field-edge);
color: var(--wikt-ink-3);
}
.dsb-queue-cancel:hover {
border-color: var(--wikt-ink-3);
color: var(--wikt-ink);
}
.dsb-queue-status {
margin-top: 6px;
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-queue-status.dsb-status-ok { color: var(--wikt-ok); }
.dsb-queue-status.dsb-status-error { color: var(--wikt-danger); }
/* ── Minerva (mobile) ────────────────────────────────────────────────
Narrower viewport: keep the panel on screen and give every tap
target the form's 44px minimum. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-quick-row {
font-size: 13px;
margin-left: 20px;
}
.dsb-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
min-height: 40px;
/* >=16px stops iOS Safari zooming the page on focus. */
font-size: 16px;
}
.dsb-tagfield .dsb-input {
min-height: 32px;
}
.dsb-tag-remove {
padding: 0 6px;
}
.dsb-save,
.dsb-cancel,
.dsb-autolink,
.dsb-queue-save,
.dsb-queue-cancel {
min-height: 40px;
padding: 8px 16px;
}
.dsb-queue-box {
top: auto;
bottom: 8px;
right: 8px;
left: 8px;
width: auto;
}
}
/* ── Dark mode ───────────────────────────────────────────────────────
Same warm dark palette and same explicit-signal-only rule as the
form's stylesheet: no bare prefers-color-scheme fallback, since
legacy Vector sets no theme class and would go dark on its own. */
html.skin-theme-clientpref-night .dsb-save,
html.skin-nightmode .dsb-save,
html.skin-theme-clientpref-night .dsb-queue-save,
html.skin-nightmode .dsb-queue-save {
color: #14213a;
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .dsb-save,
html.skin-theme-clientpref-os .dsb-queue-save {
color: #14213a;
}
}
/* Rules the menu's three sections apart (see MENU_GROUPS). */
.dsb-more-menu .dsb-menu-group-start {
margin-top: 7px;
padding-top: 7px;
border-top: 1px solid var(--wikt-rule-soft);
}
/* Deleting a sense is destructive, so it reads in the danger colour -- but it shares the last section
with "Maneyeke din zêde bike" (both act on the sense itself), so it carries no rule of its own. */
.dsb-more-menu .dsb-field-delete {
color: var(--wikt-danger);
}
.dsb-more-menu .dsb-field-delete:hover,
.dsb-more-menu .dsb-field-delete:focus {
color: var(--wikt-danger);
text-decoration: underline;
}
/* A sense queued for deletion, struck through until the queue is saved or the change dropped. */
li.dsb-sense-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
li.dsb-sense-doomed .dsb-quick-row {
text-decoration: none;
}
/* The row a new sense gets before it is saved: numbered by the list it sits in, with its own box. */
.dsb-new-sense-row .dsb-new-sense {
margin: 4px 0 8px;
}
/* The pencil on a pending sense stays visible: elsewhere it appears on hover, but here it is the only
way back into the box, and the row is a preview rather than page content. */
.dsb-new-sense-pending .dsb-toolbar .dsb-toggle {
opacity: 1;
}
/* "Maneyê rake" sits apart from the two buttons it must not be mistaken for. */
.dsb-new-sense .dsb-field-delete {
margin-inline-start: auto;
}
.dsb-new-sense-quote {
margin-top: 4px;
padding: 4px 6px;
border-radius: var(--wikt-r-field, 6px);
background: var(--wikt-code-bg);
font-family: monospace;
font-size: 12px;
overflow-wrap: anywhere;
}
/* The word lists a not-yet-saved sense carries, and the links that add them. */
/* Same row of actions a saved sense has, so the menu can overlay rather than push the row down. */
.dsb-new-sense-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 12px;
margin-top: 6px;
}
.dsb-new-sense-lists:not(:empty) {
margin-top: 8px;
}
/* A quotation or example queued for removal, marked where it sits like a doomed sense. */
.dsb-example-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
opacity: 0.75;
}
/* Queue confirmation for row actions that have no field box of their own. */
.dsb-quick-note {
display: block;
margin-top: 6px;
}
/* Dropped by anything that cannot live in a 520px column: a long etymology, and a word list wide
enough to have gone to two columns. */
.dsb-subsection-panel.dsb-panel-wide {
max-width: none;
}
/* Word-list groups: one "{{kol3}}" per block, told apart only when there is more than one. */
.dsb-wordgroups-many .dsb-wordgroup {
padding-top: 10px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-wordgroups-many .dsb-wordgroup:first-child {
padding-top: 0;
border-top: 0;
}
.dsb-group-remove {
color: var(--wikt-danger);
}
/* A sernav-headed group inside an unsaved-section preview. */
.dsb-preview-subheading {
margin-top: 6px;
font-weight: 600;
}
/* ── Werger box form ──
The {{werger-ser}} parameters. Reuses .dsb-input / .dsb-adv-label from the other panels; only the
note and the disclosure body need anything of their own. */
.dsb-werger-note {
margin: 0 0 10px;
color: var(--wikt-muted);
font-size: 12px;
line-height: 1.5;
}
.dsb-werger-main {
display: flex;
flex-direction: column;
gap: 10px;
}
/* The box is nested inside its label, so it is made a block to sit under the label text instead of
beside it -- inline, a short label like "id" ends up behind its own box. */
.dsb-werger-row .dsb-adv-label {
display: block;
margin-bottom: 0;
}
.dsb-werger-row .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* ── Îzaha maneyî ──
Opens under the Mane box; the two are one gloss, so it sits tight against it. */
.dsb-izah-wrap {
margin-top: 6px;
}
.dsb-izah-wrap .dsb-adv-label {
display: block;
}
.dsb-izah-wrap .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* A sense redrawn from the change that is queued for it, or a whole new sense waiting in the list's own
numbering. No badge and no frame: the point is that it reads the way it will read once saved. The tint
is the only sign, and the queue box is what actually says nothing is published yet. */
.dsb-sense-preview {
background: var(--wikt-accent-tint);
box-shadow: -3px 0 0 var(--wikt-accent-edge);
}
/* Kept in the tree so another gadget's form still has somewhere to open, but out of sight while the
preview above draws the same content. */
.dsb-preview-hidden {
display: none !important;
}
li.dsb-sense-preview {
display: list-item;
padding-left: 4px;
}
/* </nowiki> */
emzwh79yv9u59rnc9xjq1xib6pwv146
6535019
6535015
2026-09-20T20:25:38Z
Wikihez
7344
DefSideBoxes: the pending sense's pencil is the inline one, at the end of its line
6535019
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.css]]
*/
/* ── Searchable select ───────────────────────────────────────────────
The native <select> is hidden inside the wrapper and still holds the value; the input filters the list. */
.dsb-ac-wrap {
position: relative;
display: block;
}
.dsb-ac-input {
width: 100%;
box-sizing: border-box;
}
.dsb-ac-results {
position: absolute;
z-index: 30;
top: 100%;
left: 0;
right: 0;
max-height: 260px;
margin: 2px 0 0;
padding: 4px;
overflow-y: auto;
list-style: none;
font-family: var(--wikt-sans);
font-size: 13px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
.dsb-ac-results li {
padding: 6px 8px;
border-radius: 6px;
cursor: pointer;
}
.dsb-ac-results li:hover,
.dsb-ac-results li.dsb-ac-active {
background: var(--wikt-accent-tint);
}
/* ── Etîmolojî editing surface ───────────────────────────────────────
A contenteditable line of wikitext: text is typed straight into it, template calls sit in it as chips. */
.dsb-tpl-surface {
min-height: 72px;
margin-top: 6px;
padding: 8px 10px;
font-family: var(--wikt-sans);
font-size: 14px;
line-height: 1.6;
color: var(--wikt-ink);
white-space: pre-wrap;
word-wrap: break-word;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-tpl-surface:focus {
outline: none;
border-color: var(--wikt-accent);
box-shadow: 0 0 0 2px var(--wikt-accent-tint);
}
/* The chips: visibly not prose, and atomic to the caret. */
/* Takes the call off the line without going through the parameter dialog -- on a phone there is no
reliable backspace onto a contenteditable="false" chip. */
.dsb-tpl-chip-remove {
position: relative;
margin-left: 5px;
padding: 0 2px;
font-family: var(--wikt-sans);
font-size: 13px;
line-height: 1;
color: var(--wikt-accent-deep);
opacity: 0.6;
cursor: pointer;
}
.dsb-tpl-chip-remove:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* The glyph stays small; the tap target grows invisibly around it. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-tpl-chip-remove {
opacity: 1;
}
.dsb-tpl-chip-remove::after {
content: "";
position: absolute;
inset: -10px -8px;
}
}
.dsb-tpl-chip {
display: inline-block;
margin: 0 1px;
padding: 1px 6px;
font-family: var(--wikt-mono);
font-size: 12.5px;
color: var(--wikt-accent-deep);
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
vertical-align: baseline;
background: var(--wikt-accent-tint);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
touch-action: manipulation;
cursor: pointer;
user-select: none;
}
.dsb-tpl-chip:hover,
.dsb-tpl-chip:focus {
background: var(--wikt-field-bg);
border-color: var(--wikt-accent);
outline: none;
}
/* ── Template parameter form ─────────────────────────────────────────
.dsb-tpl-name/-desc/-param-row/-help/-undocumented/-modifier-list and the deprecated-label override now
live in cite.css (wikt.cite's own stylesheet): buildTemplateParamForm() moved there so start.js's own
Etîmolojî/Çavkanî pickers can open the same dialog. */
/* One-line variant: label beside its box rather than above it, for a single field whose name would
otherwise read as a heading for everything below it (the word groups' "Sernav"). */
.dsb-param-row-inline > .dsb-adv-label {
display: flex;
gap: 8px;
align-items: center;
}
/* Sernav is optional and names the group; the word boxes under it are what the form is actually for.
Letting it stretch to the full row made the one thing you may leave blank the widest box on the
card, so it takes only the space its own content needs. */
.dsb-param-row-inline .dsb-input {
flex: 0 1 auto;
width: 12em;
max-width: 40%;
/* .dsb-input's own 16em floor would win over the width above and make this the widest box on the
card, which is the opposite of the point. */
min-width: 0;
max-width: 100%;
min-width: 0;
}
.dsb-gloss-raw {
font-size: 12px;
}
/* Disclosure arrow on "Hevmane û Bêhtir", flipped when the menu is open. */
.dsb-more-arrow {
font-size: 10px;
vertical-align: 1px;
}
/* The POS panel's follow-up questions (cinsiyet / lektur): label above its select, like the field boxes. */
.dsb-pos-param-row {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 8px;
}
/* ── "Guhartina pêşketî" (advanced editing) panel ──────────────────────
Revealed by the pencil toggle above: every per-sense action this gadget offers, queued together with one
"Lê zêde bike". A plain inline row, not a bordered/background card. */
.dsb-quick-row {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
box-sizing: border-box;
max-width: 520px;
margin: 4px 0 0 16px;
font-family: var(--wikt-sans);
font-size: 12px;
color: var(--wikt-ink-2);
}
.dsb-quick-link {
align-self: flex-start;
margin-bottom: 10px;
font-size: 12px;
color: var(--wikt-accent);
text-decoration: none;
white-space: nowrap;
cursor: pointer;
}
/* The links under a word list ("+ Komek lê zêde bike", "Wekî wîkîtext biguhêre") sit in the field wrap
rather than in a flex column, so they need their own spacing or they run together. */
.dsb-subsection-field > .dsb-quick-link {
display: inline-block;
margin-right: 14px;
}
.dsb-quick-link:hover,
.dsb-quick-link:focus {
color: var(--wikt-accent-deep);
text-decoration: underline;
}
/* The picker row: gloss + the two primary word groups as direct "+ <field>" links, plus a "Hevmane û Bêhtir" toggle
for the rest -- clicking a link opens its box above the buttons and hides the link; .dsb-box-remove un-hides it.
position: relative anchors .dsb-more-menu's overlay below (see there). */
.dsb-field-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 4px 14px;
}
.dsb-field-picker .dsb-quick-link {
margin-bottom: 0;
}
/* Under the buttons once a box is open: a little space so the links do not read as part of the buttons. */
.dsb-open-boxes:not(:empty) ~ .dsb-field-picker {
margin-top: 8px;
}
/* The "Hevmane û Bêhtir" dropdown: every non-primary field link (plus Nimûne/jêgirtin, which sits first) stacked as a
plain vertical list. Floats over the page as an overlay (positioned against .dsb-field-picker) instead of
pushing $openBoxes/the rest of the definition down when it opens. */
.dsb-more-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 10;
display: flex;
flex-direction: column;
align-items: flex-start;
width: max-content;
max-width: min(320px, calc(100vw - 40px));
gap: 6px;
margin: 2px 0 0 4px;
padding: 8px 12px;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-field);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
}
.dsb-more-menu .dsb-quick-link {
margin-bottom: 0;
}
/* Boxes the user has actually opened, stacked in the order they were added -- several can be open at once. */
.dsb-open-boxes {
display: flex;
flex-direction: column;
}
/* Nothing open: take it out of the flow so the links do not sit above an empty gap. */
.dsb-open-boxes:empty {
display: none;
}
/* One field (its label + input) within the panel -- always a currently-open, removable box. */
.dsb-adv-group {
position: relative;
margin-bottom: 14px;
padding-right: 20px;
}
.dsb-adv-label {
margin-bottom: 4px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
/* The example sentence beside a word list's name: same text the wizard shows, so it reads as the
quieter half of the label rather than a second heading. */
.dsb-adv-hint {
margin-left: 6px;
font-weight: 400;
font-style: italic;
color: var(--wikt-muted);
}
/* The shared image rows (util.js's buildImageListField): one label + box per line, like the parameter
rows above them. Three identical boxes twice over is unreadable without a break between them, so each
image is its own block with its own name. */
.dsb-resim-list {
margin: 0;
padding-left: 0;
}
.dsb-resim-item {
list-style: none;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-resim-item:first-child {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.dsb-resim-index {
margin-bottom: 6px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
.dsb-box-remove {
position: absolute;
top: 0;
right: 0;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-box-remove:hover,
.dsb-box-remove:focus {
color: var(--wikt-accent-deep);
}
.dsb-form-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.dsb-input {
box-sizing: border-box;
min-width: 16em;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
/* The native corner is replaced by util.js's grip (see wikt-edit.css), which drags vertically only and
remembers the height. */
.dsb-subsection-panel textarea.dsb-input,
.dsb-adv-group textarea.dsb-input {
resize: none;
}
.dsb-input::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
.dsb-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* ── Plain tag field (Etîket, Kategorî) ────────────────────────────────
Plain type-and-commit tags with no suggestion panel; unlike Hevmane/Dijmane/etc., these carry no modifiers. */
.dsb-tagfield {
box-sizing: border-box;
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
min-width: 16em;
padding: 4px 6px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.dsb-tagfield:focus-within {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
}
.dsb-tag {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
padding: 3px 6px 3px 10px;
}
.dsb-tag-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
.dsb-tag-remove:hover {
color: var(--wikt-danger);
}
.dsb-tagfield .dsb-input {
flex: 1 1 8em;
min-width: 8em;
border: 0;
padding: 3px 2px;
background: transparent;
box-shadow: none;
}
.dsb-tagfield .dsb-input:focus {
box-shadow: none;
}
.dsb-save,
.dsb-cancel,
.dsb-delete,
.dsb-autolink {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
/* One filled pill per row — the same rule the form's footer follows. */
.dsb-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-cancel {
background: transparent;
border: 1px solid transparent;
color: var(--wikt-ink-3);
}
.dsb-cancel:hover {
border-color: var(--wikt-field-edge);
color: var(--wikt-ink);
}
/* Removing a heading: plain like Betal bike, in the danger colour, pushed to the far end of the row. */
.dsb-delete {
margin-left: auto;
background: transparent;
border: 1px solid transparent;
color: var(--wikt-danger);
}
.dsb-delete:hover {
border-color: var(--wikt-danger);
}
/* The heading's name, above the section's own box. */
.dsb-heading-name-wrap {
margin-bottom: 10px;
}
/* A heading queued for removal, with everything it owns, until the queue is saved or the change dropped. */
.dsb-preview-removed {
opacity: 0.5;
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
/* An outline pill, distinct from both the filled .dsb-save and the plain-text .dsb-cancel -- a secondary
utility action, not the primary save nor a dismissal. */
.dsb-autolink {
background: transparent;
border: 1px solid var(--wikt-accent-edge);
color: var(--wikt-accent);
}
.dsb-autolink:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-autolink:disabled {
opacity: 0.55;
cursor: progress;
}
/* Status line. Color comes from .dsb-status-ok/.dsb-status-error (added by the .js on success/failure)
rather than an inline style, so these tokens (and dark mode) apply. */
.dsb-status {
font-family: var(--wikt-sans);
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-status-ok { color: var(--wikt-ok); }
.dsb-status-error { color: var(--wikt-danger); }
/* ── "+ Beşek lê zêde bike" ──────────────────────────────────────────
An accent-coloured action link, like the form's .wiktku-adv-toggle:
it opens something, so it is not muted grey. */
/* iOS Safari zooms the page in whenever a field it focuses has text under 16px, and everything here is
12-13px for density. On touch the form controls go up to the size that stops it; the pointer query
leaves the desktop layout alone. */
@media (pointer: coarse) {
.dsb-input,
.dsb-tag-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
font-size: 16px;
}
/* Full width, whatever the viewport reports: a phone asked to render the desktop site says ~980px and
would otherwise leave these at their desktop size. */
.dsb-subsection-panel .dsb-input,
.dsb-subsection-panel .dsb-subsection-select,
.dsb-convert-pos-panel .dsb-convert-pos-select {
width: 100%;
max-width: 100%;
}
/* Sernav is the exception: it names the group, so it stays visibly smaller than the boxes it heads.
The cap matters more than the em width -- at 16px, 12em came out the same size as a word box. */
.dsb-subsection-panel .dsb-param-row-inline .dsb-input {
width: 8em;
max-width: 40%;
}
}
/* The image rows reuse the inline row, but nothing here is optional the way a group's "Sernav" is:
a file name and a caption want the whole row. */
.dsb-resim-list .dsb-param-row-inline > .dsb-adv-label,
.dsb-resim-list .dsb-tpl-param-row > label {
display: flex;
gap: 8px;
align-items: center;
}
.dsb-resim-list .dsb-param-row-inline .dsb-input,
.dsb-resim-list .dsb-tpl-param-row .dsb-input {
flex: 1 1 auto;
width: auto;
max-width: 100%;
}
/* ── Subsection panel ────────────────────────────────────────────────
A sunken card rather than a grey utility box; fields stack so the
picker and its input are not competing for one narrow row. */
.dsb-subsection-select,
.dsb-convert-pos-select {
box-sizing: border-box;
width: 100%;
max-width: 100%;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-subsection-select:focus,
.dsb-convert-pos-select:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The template-insert picker sits above the Çavkanî textarea, not in place of it -- a little breathing
room so the two don't read as one control. */
.dsb-cavkani-picker {
margin-bottom: 6px;
}
.dsb-subsection-field {
min-width: 0;
}
.dsb-subsection-words,
.dsb-subsection-text {
box-sizing: border-box;
width: 100%;
min-width: 0;
}
.dsb-subsection-text {
line-height: 1.6;
resize: vertical;
}
/* ── Gloss field inside the advanced-editing panel ────────────────────
Same shape as .dsb-subsection-text. */
.dsb-edit-gloss {
width: 100%;
line-height: 1.6;
resize: vertical;
}
/* ── Live "not saved yet" previews ─────────────────────────────────────
Dropped into the rendered page at the change's own spot. Dashed edge + a small badge mark them as
pending, not real content; they disappear on "Betal bike" or once the real save lands. */
.dsb-preview-block,
.dsb-preview-inline {
margin: 8px 0;
padding: 8px 12px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-accent-tint);
border: 1px dashed var(--wikt-accent-edge);
border-radius: var(--wikt-r-field);
}
.dsb-preview-inline {
margin-left: 26px;
max-width: 520px;
}
.dsb-preview-heading {
margin: 0 0 4px;
font-size: 15px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-preview-badge {
display: inline-block;
margin-bottom: 6px;
padding: 1px 8px;
font-size: 11px;
font-weight: 500;
color: var(--wikt-accent-deep);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
}
/* Parser output goes in here, so it drops the panel's own font and colour and takes the page's \u2014 the
point of the box is that what is inside it looks exactly like what will be saved. */
.dsb-preview-rendered,
.dsb-preview-rendered-inline {
font-family: inherit;
font-size: 14px;
color: var(--wikt-ink);
}
.dsb-preview-rendered-inline {
display: inline;
}
.dsb-preview-rendered-inline .mw-parser-output,
.dsb-preview-rendered-inline .mw-parser-output > p {
display: inline;
}
.dsb-preview-list,
.dsb-preview-text {
margin: 0;
padding-left: 18px;
}
.dsb-preview-text {
padding-left: 0;
}
/* ── Floating save-queue widget ─────────────────────────────────────
This gadget's own small floating box. One "Lê zêde bike" click anywhere on the page adds a line here;
"Guhertinan qeyd bike" makes the one real save; "Betal bike" drops everything queued without touching the page. */
.dsb-queue-box {
position: fixed;
top: 60px;
right: 16px;
z-index: 1000;
box-sizing: border-box;
width: 280px;
max-width: calc(100vw - 32px);
max-height: calc(100vh - 90px);
overflow-y: auto;
padding: 12px 14px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent);
border-radius: var(--wikt-r-card);
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.16);
}
/* An OOUI dialog sits low in the page's stacking order (z-index 4), so no z-index here can go under it
without dropping behind the article as well. While one is open -- on a phone it fills the screen --
the box is taken off instead; wikt.cite puts the class on while its dialogs are showing. */
body.wikt-dialog-open .dsb-queue-box {
display: none;
}
.dsb-queue-title {
margin-right: 20px;
font-size: 13px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-queue-close {
float: right;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-queue-close:hover,
.dsb-queue-close:focus {
color: var(--wikt-accent-deep);
}
/* Stands in for the list before anything is queued, saying what the save button is for. */
.dsb-queue-empty {
margin: 8px 0;
font-size: 12px;
line-height: 1.5;
color: var(--wikt-ink-3);
}
.dsb-queue-list {
margin: 8px 0;
padding-left: 18px;
max-height: 160px;
overflow-y: auto;
}
.dsb-queue-list li {
margin-bottom: 4px;
}
/* Drops one queued change. Dim until its line is hovered, so the list stays a list and not a row of
buttons. */
.dsb-queue-drop {
margin-left: 6px;
padding: 0 3px;
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
opacity: 0;
transition: opacity 0.12s ease, color 0.12s ease;
}
.dsb-queue-list li:hover .dsb-queue-drop,
.dsb-queue-drop:focus {
opacity: 1;
}
.dsb-queue-drop:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* No hover on touch, so the button is simply there. */
@media (hover: none), (pointer: coarse) {
.dsb-queue-drop {
opacity: 1;
}
}
.dsb-queue-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
}
.dsb-queue-save,
.dsb-queue-cancel {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.dsb-queue-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-queue-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-queue-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-queue-cancel {
background: transparent;
border: 1px solid var(--wikt-field-edge);
color: var(--wikt-ink-3);
}
.dsb-queue-cancel:hover {
border-color: var(--wikt-ink-3);
color: var(--wikt-ink);
}
.dsb-queue-status {
margin-top: 6px;
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-queue-status.dsb-status-ok { color: var(--wikt-ok); }
.dsb-queue-status.dsb-status-error { color: var(--wikt-danger); }
/* ── Minerva (mobile) ────────────────────────────────────────────────
Narrower viewport: keep the panel on screen and give every tap
target the form's 44px minimum. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-quick-row {
font-size: 13px;
margin-left: 20px;
}
.dsb-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
min-height: 40px;
/* >=16px stops iOS Safari zooming the page on focus. */
font-size: 16px;
}
.dsb-tagfield .dsb-input {
min-height: 32px;
}
.dsb-tag-remove {
padding: 0 6px;
}
.dsb-save,
.dsb-cancel,
.dsb-autolink,
.dsb-queue-save,
.dsb-queue-cancel {
min-height: 40px;
padding: 8px 16px;
}
.dsb-queue-box {
top: auto;
bottom: 8px;
right: 8px;
left: 8px;
width: auto;
}
}
/* ── Dark mode ───────────────────────────────────────────────────────
Same warm dark palette and same explicit-signal-only rule as the
form's stylesheet: no bare prefers-color-scheme fallback, since
legacy Vector sets no theme class and would go dark on its own. */
html.skin-theme-clientpref-night .dsb-save,
html.skin-nightmode .dsb-save,
html.skin-theme-clientpref-night .dsb-queue-save,
html.skin-nightmode .dsb-queue-save {
color: #14213a;
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .dsb-save,
html.skin-theme-clientpref-os .dsb-queue-save {
color: #14213a;
}
}
/* Rules the menu's three sections apart (see MENU_GROUPS). */
.dsb-more-menu .dsb-menu-group-start {
margin-top: 7px;
padding-top: 7px;
border-top: 1px solid var(--wikt-rule-soft);
}
/* Deleting a sense is destructive, so it reads in the danger colour -- but it shares the last section
with "Maneyeke din zêde bike" (both act on the sense itself), so it carries no rule of its own. */
.dsb-more-menu .dsb-field-delete {
color: var(--wikt-danger);
}
.dsb-more-menu .dsb-field-delete:hover,
.dsb-more-menu .dsb-field-delete:focus {
color: var(--wikt-danger);
text-decoration: underline;
}
/* A sense queued for deletion, struck through until the queue is saved or the change dropped. */
li.dsb-sense-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
li.dsb-sense-doomed .dsb-quick-row {
text-decoration: none;
}
/* The row a new sense gets before it is saved: numbered by the list it sits in, with its own box. */
.dsb-new-sense-row .dsb-new-sense {
margin: 4px 0 8px;
}
/* The pencil on a pending sense stays visible: elsewhere it appears on hover, but here it is the only
way back into the box, and the row is a preview rather than page content. */
.dsb-new-sense-pending .dsb-example-toolbar .dsb-toggle {
opacity: 1;
}
/* "Maneyê rake" sits apart from the two buttons it must not be mistaken for. */
.dsb-new-sense .dsb-field-delete {
margin-inline-start: auto;
}
.dsb-new-sense-quote {
margin-top: 4px;
padding: 4px 6px;
border-radius: var(--wikt-r-field, 6px);
background: var(--wikt-code-bg);
font-family: monospace;
font-size: 12px;
overflow-wrap: anywhere;
}
/* The word lists a not-yet-saved sense carries, and the links that add them. */
/* Same row of actions a saved sense has, so the menu can overlay rather than push the row down. */
.dsb-new-sense-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 12px;
margin-top: 6px;
}
.dsb-new-sense-lists:not(:empty) {
margin-top: 8px;
}
/* A quotation or example queued for removal, marked where it sits like a doomed sense. */
.dsb-example-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
opacity: 0.75;
}
/* Queue confirmation for row actions that have no field box of their own. */
.dsb-quick-note {
display: block;
margin-top: 6px;
}
/* Dropped by anything that cannot live in a 520px column: a long etymology, and a word list wide
enough to have gone to two columns. */
.dsb-subsection-panel.dsb-panel-wide {
max-width: none;
}
/* Word-list groups: one "{{kol3}}" per block, told apart only when there is more than one. */
.dsb-wordgroups-many .dsb-wordgroup {
padding-top: 10px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-wordgroups-many .dsb-wordgroup:first-child {
padding-top: 0;
border-top: 0;
}
.dsb-group-remove {
color: var(--wikt-danger);
}
/* A sernav-headed group inside an unsaved-section preview. */
.dsb-preview-subheading {
margin-top: 6px;
font-weight: 600;
}
/* ── Werger box form ──
The {{werger-ser}} parameters. Reuses .dsb-input / .dsb-adv-label from the other panels; only the
note and the disclosure body need anything of their own. */
.dsb-werger-note {
margin: 0 0 10px;
color: var(--wikt-muted);
font-size: 12px;
line-height: 1.5;
}
.dsb-werger-main {
display: flex;
flex-direction: column;
gap: 10px;
}
/* The box is nested inside its label, so it is made a block to sit under the label text instead of
beside it -- inline, a short label like "id" ends up behind its own box. */
.dsb-werger-row .dsb-adv-label {
display: block;
margin-bottom: 0;
}
.dsb-werger-row .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* ── Îzaha maneyî ──
Opens under the Mane box; the two are one gloss, so it sits tight against it. */
.dsb-izah-wrap {
margin-top: 6px;
}
.dsb-izah-wrap .dsb-adv-label {
display: block;
}
.dsb-izah-wrap .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* A sense redrawn from the change that is queued for it, or a whole new sense waiting in the list's own
numbering. No badge and no frame: the point is that it reads the way it will read once saved. The tint
is the only sign, and the queue box is what actually says nothing is published yet. */
.dsb-sense-preview {
background: var(--wikt-accent-tint);
box-shadow: -3px 0 0 var(--wikt-accent-edge);
}
/* Kept in the tree so another gadget's form still has somewhere to open, but out of sight while the
preview above draws the same content. */
.dsb-preview-hidden {
display: none !important;
}
li.dsb-sense-preview {
display: list-item;
padding-left: 4px;
}
/* </nowiki> */
ry8mo81m7omrf8qz8tbmnkidy4t5nib
6535021
6535019
2026-09-20T20:34:25Z
Wikihez
7344
DefSideBoxes: a new sense is built with the sense panel itself
6535021
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.css]]
*/
/* ── Searchable select ───────────────────────────────────────────────
The native <select> is hidden inside the wrapper and still holds the value; the input filters the list. */
.dsb-ac-wrap {
position: relative;
display: block;
}
.dsb-ac-input {
width: 100%;
box-sizing: border-box;
}
.dsb-ac-results {
position: absolute;
z-index: 30;
top: 100%;
left: 0;
right: 0;
max-height: 260px;
margin: 2px 0 0;
padding: 4px;
overflow-y: auto;
list-style: none;
font-family: var(--wikt-sans);
font-size: 13px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
.dsb-ac-results li {
padding: 6px 8px;
border-radius: 6px;
cursor: pointer;
}
.dsb-ac-results li:hover,
.dsb-ac-results li.dsb-ac-active {
background: var(--wikt-accent-tint);
}
/* ── Etîmolojî editing surface ───────────────────────────────────────
A contenteditable line of wikitext: text is typed straight into it, template calls sit in it as chips. */
.dsb-tpl-surface {
min-height: 72px;
margin-top: 6px;
padding: 8px 10px;
font-family: var(--wikt-sans);
font-size: 14px;
line-height: 1.6;
color: var(--wikt-ink);
white-space: pre-wrap;
word-wrap: break-word;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-tpl-surface:focus {
outline: none;
border-color: var(--wikt-accent);
box-shadow: 0 0 0 2px var(--wikt-accent-tint);
}
/* The chips: visibly not prose, and atomic to the caret. */
/* Takes the call off the line without going through the parameter dialog -- on a phone there is no
reliable backspace onto a contenteditable="false" chip. */
.dsb-tpl-chip-remove {
position: relative;
margin-left: 5px;
padding: 0 2px;
font-family: var(--wikt-sans);
font-size: 13px;
line-height: 1;
color: var(--wikt-accent-deep);
opacity: 0.6;
cursor: pointer;
}
.dsb-tpl-chip-remove:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* The glyph stays small; the tap target grows invisibly around it. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-tpl-chip-remove {
opacity: 1;
}
.dsb-tpl-chip-remove::after {
content: "";
position: absolute;
inset: -10px -8px;
}
}
.dsb-tpl-chip {
display: inline-block;
margin: 0 1px;
padding: 1px 6px;
font-family: var(--wikt-mono);
font-size: 12.5px;
color: var(--wikt-accent-deep);
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
vertical-align: baseline;
background: var(--wikt-accent-tint);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
touch-action: manipulation;
cursor: pointer;
user-select: none;
}
.dsb-tpl-chip:hover,
.dsb-tpl-chip:focus {
background: var(--wikt-field-bg);
border-color: var(--wikt-accent);
outline: none;
}
/* ── Template parameter form ─────────────────────────────────────────
.dsb-tpl-name/-desc/-param-row/-help/-undocumented/-modifier-list and the deprecated-label override now
live in cite.css (wikt.cite's own stylesheet): buildTemplateParamForm() moved there so start.js's own
Etîmolojî/Çavkanî pickers can open the same dialog. */
/* One-line variant: label beside its box rather than above it, for a single field whose name would
otherwise read as a heading for everything below it (the word groups' "Sernav"). */
.dsb-param-row-inline > .dsb-adv-label {
display: flex;
gap: 8px;
align-items: center;
}
/* Sernav is optional and names the group; the word boxes under it are what the form is actually for.
Letting it stretch to the full row made the one thing you may leave blank the widest box on the
card, so it takes only the space its own content needs. */
.dsb-param-row-inline .dsb-input {
flex: 0 1 auto;
width: 12em;
max-width: 40%;
/* .dsb-input's own 16em floor would win over the width above and make this the widest box on the
card, which is the opposite of the point. */
min-width: 0;
max-width: 100%;
min-width: 0;
}
.dsb-gloss-raw {
font-size: 12px;
}
/* Disclosure arrow on "Hevmane û Bêhtir", flipped when the menu is open. */
.dsb-more-arrow {
font-size: 10px;
vertical-align: 1px;
}
/* The POS panel's follow-up questions (cinsiyet / lektur): label above its select, like the field boxes. */
.dsb-pos-param-row {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 8px;
}
/* ── "Guhartina pêşketî" (advanced editing) panel ──────────────────────
Revealed by the pencil toggle above: every per-sense action this gadget offers, queued together with one
"Lê zêde bike". A plain inline row, not a bordered/background card. */
.dsb-quick-row {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
box-sizing: border-box;
max-width: 520px;
margin: 4px 0 0 16px;
font-family: var(--wikt-sans);
font-size: 12px;
color: var(--wikt-ink-2);
}
.dsb-quick-link {
align-self: flex-start;
margin-bottom: 10px;
font-size: 12px;
color: var(--wikt-accent);
text-decoration: none;
white-space: nowrap;
cursor: pointer;
}
/* The links under a word list ("+ Komek lê zêde bike", "Wekî wîkîtext biguhêre") sit in the field wrap
rather than in a flex column, so they need their own spacing or they run together. */
.dsb-subsection-field > .dsb-quick-link {
display: inline-block;
margin-right: 14px;
}
.dsb-quick-link:hover,
.dsb-quick-link:focus {
color: var(--wikt-accent-deep);
text-decoration: underline;
}
/* The picker row: gloss + the two primary word groups as direct "+ <field>" links, plus a "Hevmane û Bêhtir" toggle
for the rest -- clicking a link opens its box above the buttons and hides the link; .dsb-box-remove un-hides it.
position: relative anchors .dsb-more-menu's overlay below (see there). */
.dsb-field-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 4px 14px;
}
.dsb-field-picker .dsb-quick-link {
margin-bottom: 0;
}
/* Under the buttons once a box is open: a little space so the links do not read as part of the buttons. */
.dsb-open-boxes:not(:empty) ~ .dsb-field-picker {
margin-top: 8px;
}
/* The "Hevmane û Bêhtir" dropdown: every non-primary field link (plus Nimûne/jêgirtin, which sits first) stacked as a
plain vertical list. Floats over the page as an overlay (positioned against .dsb-field-picker) instead of
pushing $openBoxes/the rest of the definition down when it opens. */
.dsb-more-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 10;
display: flex;
flex-direction: column;
align-items: flex-start;
width: max-content;
max-width: min(320px, calc(100vw - 40px));
gap: 6px;
margin: 2px 0 0 4px;
padding: 8px 12px;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-field);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
}
.dsb-more-menu .dsb-quick-link {
margin-bottom: 0;
}
/* Boxes the user has actually opened, stacked in the order they were added -- several can be open at once. */
.dsb-open-boxes {
display: flex;
flex-direction: column;
}
/* Nothing open: take it out of the flow so the links do not sit above an empty gap. */
.dsb-open-boxes:empty {
display: none;
}
/* One field (its label + input) within the panel -- always a currently-open, removable box. */
.dsb-adv-group {
position: relative;
margin-bottom: 14px;
padding-right: 20px;
}
.dsb-adv-label {
margin-bottom: 4px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
/* The example sentence beside a word list's name: same text the wizard shows, so it reads as the
quieter half of the label rather than a second heading. */
.dsb-adv-hint {
margin-left: 6px;
font-weight: 400;
font-style: italic;
color: var(--wikt-muted);
}
/* The shared image rows (util.js's buildImageListField): one label + box per line, like the parameter
rows above them. Three identical boxes twice over is unreadable without a break between them, so each
image is its own block with its own name. */
.dsb-resim-list {
margin: 0;
padding-left: 0;
}
.dsb-resim-item {
list-style: none;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-resim-item:first-child {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.dsb-resim-index {
margin-bottom: 6px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
.dsb-box-remove {
position: absolute;
top: 0;
right: 0;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-box-remove:hover,
.dsb-box-remove:focus {
color: var(--wikt-accent-deep);
}
.dsb-form-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.dsb-input {
box-sizing: border-box;
min-width: 16em;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
/* The native corner is replaced by util.js's grip (see wikt-edit.css), which drags vertically only and
remembers the height. */
.dsb-subsection-panel textarea.dsb-input,
.dsb-adv-group textarea.dsb-input {
resize: none;
}
.dsb-input::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
.dsb-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* ── Plain tag field (Etîket, Kategorî) ────────────────────────────────
Plain type-and-commit tags with no suggestion panel; unlike Hevmane/Dijmane/etc., these carry no modifiers. */
.dsb-tagfield {
box-sizing: border-box;
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
min-width: 16em;
padding: 4px 6px;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.dsb-tagfield:focus-within {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
}
.dsb-tag {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
padding: 3px 6px 3px 10px;
}
.dsb-tag-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
.dsb-tag-remove:hover {
color: var(--wikt-danger);
}
.dsb-tagfield .dsb-input {
flex: 1 1 8em;
min-width: 8em;
border: 0;
padding: 3px 2px;
background: transparent;
box-shadow: none;
}
.dsb-tagfield .dsb-input:focus {
box-shadow: none;
}
.dsb-save,
.dsb-cancel,
.dsb-delete,
.dsb-autolink {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
/* One filled pill per row — the same rule the form's footer follows. */
.dsb-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-cancel {
background: transparent;
border: 1px solid transparent;
color: var(--wikt-ink-3);
}
.dsb-cancel:hover {
border-color: var(--wikt-field-edge);
color: var(--wikt-ink);
}
/* Removing a heading: plain like Betal bike, in the danger colour, pushed to the far end of the row. */
.dsb-delete {
margin-left: auto;
background: transparent;
border: 1px solid transparent;
color: var(--wikt-danger);
}
.dsb-delete:hover {
border-color: var(--wikt-danger);
}
/* The heading's name, above the section's own box. */
.dsb-heading-name-wrap {
margin-bottom: 10px;
}
/* A heading queued for removal, with everything it owns, until the queue is saved or the change dropped. */
.dsb-preview-removed {
opacity: 0.5;
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
/* An outline pill, distinct from both the filled .dsb-save and the plain-text .dsb-cancel -- a secondary
utility action, not the primary save nor a dismissal. */
.dsb-autolink {
background: transparent;
border: 1px solid var(--wikt-accent-edge);
color: var(--wikt-accent);
}
.dsb-autolink:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-autolink:disabled {
opacity: 0.55;
cursor: progress;
}
/* Status line. Color comes from .dsb-status-ok/.dsb-status-error (added by the .js on success/failure)
rather than an inline style, so these tokens (and dark mode) apply. */
.dsb-status {
font-family: var(--wikt-sans);
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-status-ok { color: var(--wikt-ok); }
.dsb-status-error { color: var(--wikt-danger); }
/* ── "+ Beşek lê zêde bike" ──────────────────────────────────────────
An accent-coloured action link, like the form's .wiktku-adv-toggle:
it opens something, so it is not muted grey. */
/* iOS Safari zooms the page in whenever a field it focuses has text under 16px, and everything here is
12-13px for density. On touch the form controls go up to the size that stops it; the pointer query
leaves the desktop layout alone. */
@media (pointer: coarse) {
.dsb-input,
.dsb-tag-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
font-size: 16px;
}
/* Full width, whatever the viewport reports: a phone asked to render the desktop site says ~980px and
would otherwise leave these at their desktop size. */
.dsb-subsection-panel .dsb-input,
.dsb-subsection-panel .dsb-subsection-select,
.dsb-convert-pos-panel .dsb-convert-pos-select {
width: 100%;
max-width: 100%;
}
/* Sernav is the exception: it names the group, so it stays visibly smaller than the boxes it heads.
The cap matters more than the em width -- at 16px, 12em came out the same size as a word box. */
.dsb-subsection-panel .dsb-param-row-inline .dsb-input {
width: 8em;
max-width: 40%;
}
}
/* The image rows reuse the inline row, but nothing here is optional the way a group's "Sernav" is:
a file name and a caption want the whole row. */
.dsb-resim-list .dsb-param-row-inline > .dsb-adv-label,
.dsb-resim-list .dsb-tpl-param-row > label {
display: flex;
gap: 8px;
align-items: center;
}
.dsb-resim-list .dsb-param-row-inline .dsb-input,
.dsb-resim-list .dsb-tpl-param-row .dsb-input {
flex: 1 1 auto;
width: auto;
max-width: 100%;
}
/* ── Subsection panel ────────────────────────────────────────────────
A sunken card rather than a grey utility box; fields stack so the
picker and its input are not competing for one narrow row. */
.dsb-subsection-select,
.dsb-convert-pos-select {
box-sizing: border-box;
width: 100%;
max-width: 100%;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
.dsb-subsection-select:focus,
.dsb-convert-pos-select:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The template-insert picker sits above the Çavkanî textarea, not in place of it -- a little breathing
room so the two don't read as one control. */
.dsb-cavkani-picker {
margin-bottom: 6px;
}
.dsb-subsection-field {
min-width: 0;
}
.dsb-subsection-words,
.dsb-subsection-text {
box-sizing: border-box;
width: 100%;
min-width: 0;
}
.dsb-subsection-text {
line-height: 1.6;
resize: vertical;
}
/* ── Gloss field inside the advanced-editing panel ────────────────────
Same shape as .dsb-subsection-text. */
.dsb-edit-gloss {
width: 100%;
line-height: 1.6;
resize: vertical;
}
/* ── Live "not saved yet" previews ─────────────────────────────────────
Dropped into the rendered page at the change's own spot. Dashed edge + a small badge mark them as
pending, not real content; they disappear on "Betal bike" or once the real save lands. */
.dsb-preview-block,
.dsb-preview-inline {
margin: 8px 0;
padding: 8px 12px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-accent-tint);
border: 1px dashed var(--wikt-accent-edge);
border-radius: var(--wikt-r-field);
}
.dsb-preview-inline {
margin-left: 26px;
max-width: 520px;
}
.dsb-preview-heading {
margin: 0 0 4px;
font-size: 15px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-preview-badge {
display: inline-block;
margin-bottom: 6px;
padding: 1px 8px;
font-size: 11px;
font-weight: 500;
color: var(--wikt-accent-deep);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
}
/* Parser output goes in here, so it drops the panel's own font and colour and takes the page's \u2014 the
point of the box is that what is inside it looks exactly like what will be saved. */
.dsb-preview-rendered,
.dsb-preview-rendered-inline {
font-family: inherit;
font-size: 14px;
color: var(--wikt-ink);
}
.dsb-preview-rendered-inline {
display: inline;
}
.dsb-preview-rendered-inline .mw-parser-output,
.dsb-preview-rendered-inline .mw-parser-output > p {
display: inline;
}
.dsb-preview-list,
.dsb-preview-text {
margin: 0;
padding-left: 18px;
}
.dsb-preview-text {
padding-left: 0;
}
/* ── Floating save-queue widget ─────────────────────────────────────
This gadget's own small floating box. One "Lê zêde bike" click anywhere on the page adds a line here;
"Guhertinan qeyd bike" makes the one real save; "Betal bike" drops everything queued without touching the page. */
.dsb-queue-box {
position: fixed;
top: 60px;
right: 16px;
z-index: 1000;
box-sizing: border-box;
width: 280px;
max-width: calc(100vw - 32px);
max-height: calc(100vh - 90px);
overflow-y: auto;
padding: 12px 14px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-card);
border: 1px solid var(--wikt-accent);
border-radius: var(--wikt-r-card);
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.16);
}
/* An OOUI dialog sits low in the page's stacking order (z-index 4), so no z-index here can go under it
without dropping behind the article as well. While one is open -- on a phone it fills the screen --
the box is taken off instead; wikt.cite puts the class on while its dialogs are showing. */
body.wikt-dialog-open .dsb-queue-box {
display: none;
}
.dsb-queue-title {
margin-right: 20px;
font-size: 13px;
font-weight: 600;
color: var(--wikt-ink);
}
.dsb-queue-close {
float: right;
color: var(--wikt-ink-3);
text-decoration: none;
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.dsb-queue-close:hover,
.dsb-queue-close:focus {
color: var(--wikt-accent-deep);
}
/* Stands in for the list before anything is queued, saying what the save button is for. */
.dsb-queue-empty {
margin: 8px 0;
font-size: 12px;
line-height: 1.5;
color: var(--wikt-ink-3);
}
.dsb-queue-list {
margin: 8px 0;
padding-left: 18px;
max-height: 160px;
overflow-y: auto;
}
.dsb-queue-list li {
margin-bottom: 4px;
}
/* Drops one queued change. Dim until its line is hovered, so the list stays a list and not a row of
buttons. */
.dsb-queue-drop {
margin-left: 6px;
padding: 0 3px;
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
opacity: 0;
transition: opacity 0.12s ease, color 0.12s ease;
}
.dsb-queue-list li:hover .dsb-queue-drop,
.dsb-queue-drop:focus {
opacity: 1;
}
.dsb-queue-drop:hover {
color: var(--wikt-danger);
opacity: 1;
}
/* No hover on touch, so the button is simply there. */
@media (hover: none), (pointer: coarse) {
.dsb-queue-drop {
opacity: 1;
}
}
.dsb-queue-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 4px;
}
.dsb-queue-save,
.dsb-queue-cancel {
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
border-radius: var(--wikt-r-pill);
cursor: pointer;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.dsb-queue-save {
background: var(--wikt-accent);
border: 1px solid var(--wikt-accent);
color: #fff;
}
.dsb-queue-save:hover {
background: var(--wikt-accent-deep);
border-color: var(--wikt-accent-deep);
}
.dsb-queue-save:disabled {
opacity: 0.55;
cursor: progress;
}
.dsb-queue-cancel {
background: transparent;
border: 1px solid var(--wikt-field-edge);
color: var(--wikt-ink-3);
}
.dsb-queue-cancel:hover {
border-color: var(--wikt-ink-3);
color: var(--wikt-ink);
}
.dsb-queue-status {
margin-top: 6px;
font-size: 12.5px;
color: var(--wikt-muted);
}
.dsb-queue-status.dsb-status-ok { color: var(--wikt-ok); }
.dsb-queue-status.dsb-status-error { color: var(--wikt-danger); }
/* ── Minerva (mobile) ────────────────────────────────────────────────
Narrower viewport: keep the panel on screen and give every tap
target the form's 44px minimum. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-quick-row {
font-size: 13px;
margin-left: 20px;
}
.dsb-input,
.dsb-subsection-select,
.dsb-convert-pos-select {
min-height: 40px;
/* >=16px stops iOS Safari zooming the page on focus. */
font-size: 16px;
}
.dsb-tagfield .dsb-input {
min-height: 32px;
}
.dsb-tag-remove {
padding: 0 6px;
}
.dsb-save,
.dsb-cancel,
.dsb-autolink,
.dsb-queue-save,
.dsb-queue-cancel {
min-height: 40px;
padding: 8px 16px;
}
.dsb-queue-box {
top: auto;
bottom: 8px;
right: 8px;
left: 8px;
width: auto;
}
}
/* ── Dark mode ───────────────────────────────────────────────────────
Same warm dark palette and same explicit-signal-only rule as the
form's stylesheet: no bare prefers-color-scheme fallback, since
legacy Vector sets no theme class and would go dark on its own. */
html.skin-theme-clientpref-night .dsb-save,
html.skin-nightmode .dsb-save,
html.skin-theme-clientpref-night .dsb-queue-save,
html.skin-nightmode .dsb-queue-save {
color: #14213a;
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .dsb-save,
html.skin-theme-clientpref-os .dsb-queue-save {
color: #14213a;
}
}
/* Rules the menu's three sections apart (see MENU_GROUPS). */
.dsb-more-menu .dsb-menu-group-start {
margin-top: 7px;
padding-top: 7px;
border-top: 1px solid var(--wikt-rule-soft);
}
/* Deleting a sense is destructive, so it reads in the danger colour -- but it shares the last section
with "Maneyeke din zêde bike" (both act on the sense itself), so it carries no rule of its own. */
.dsb-more-menu .dsb-field-delete {
color: var(--wikt-danger);
}
.dsb-more-menu .dsb-field-delete:hover,
.dsb-more-menu .dsb-field-delete:focus {
color: var(--wikt-danger);
text-decoration: underline;
}
/* A sense queued for deletion, struck through until the queue is saved or the change dropped. */
li.dsb-sense-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
}
li.dsb-sense-doomed .dsb-quick-row {
text-decoration: none;
}
/* The row a new sense gets before it is saved: numbered by the list it sits in, with its own box. */
.dsb-new-sense-row .dsb-new-sense {
margin: 4px 0 8px;
}
/* The pencil on a pending sense stays visible: elsewhere it appears on hover, but here it is the only
way back into the box, and the row is a preview rather than page content. */
.dsb-new-sense-pending .dsb-example-toolbar .dsb-toggle {
opacity: 1;
}
/* add-examples' own title, taken out of the sense's example list so it carries no number. */
.dsb-new-sense-form-title {
margin-top: 6px;
font-weight: bold;
}
.dsb-new-sense-quote {
margin-top: 4px;
padding: 4px 6px;
border-radius: var(--wikt-r-field, 6px);
background: var(--wikt-code-bg);
font-family: monospace;
font-size: 12px;
overflow-wrap: anywhere;
}
/* The word lists a not-yet-saved sense carries, and the links that add them. */
/* Same row of actions a saved sense has, so the menu can overlay rather than push the row down. */
.dsb-new-sense-picker {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 12px;
margin-top: 6px;
}
.dsb-new-sense-lists:not(:empty) {
margin-top: 8px;
}
/* A quotation or example queued for removal, marked where it sits like a doomed sense. */
.dsb-example-doomed {
text-decoration: line-through;
text-decoration-color: var(--wikt-danger);
opacity: 0.75;
}
/* Queue confirmation for row actions that have no field box of their own. */
.dsb-quick-note {
display: block;
margin-top: 6px;
}
/* Dropped by anything that cannot live in a 520px column: a long etymology, and a word list wide
enough to have gone to two columns. */
.dsb-subsection-panel.dsb-panel-wide {
max-width: none;
}
/* Word-list groups: one "{{kol3}}" per block, told apart only when there is more than one. */
.dsb-wordgroups-many .dsb-wordgroup {
padding-top: 10px;
border-top: 1px solid var(--wikt-rule-soft);
}
.dsb-wordgroups-many .dsb-wordgroup:first-child {
padding-top: 0;
border-top: 0;
}
.dsb-group-remove {
color: var(--wikt-danger);
}
/* A sernav-headed group inside an unsaved-section preview. */
.dsb-preview-subheading {
margin-top: 6px;
font-weight: 600;
}
/* ── Werger box form ──
The {{werger-ser}} parameters. Reuses .dsb-input / .dsb-adv-label from the other panels; only the
note and the disclosure body need anything of their own. */
.dsb-werger-note {
margin: 0 0 10px;
color: var(--wikt-muted);
font-size: 12px;
line-height: 1.5;
}
.dsb-werger-main {
display: flex;
flex-direction: column;
gap: 10px;
}
/* The box is nested inside its label, so it is made a block to sit under the label text instead of
beside it -- inline, a short label like "id" ends up behind its own box. */
.dsb-werger-row .dsb-adv-label {
display: block;
margin-bottom: 0;
}
.dsb-werger-row .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* ── Îzaha maneyî ──
Opens under the Mane box; the two are one gloss, so it sits tight against it. */
.dsb-izah-wrap {
margin-top: 6px;
}
.dsb-izah-wrap .dsb-adv-label {
display: block;
}
.dsb-izah-wrap .dsb-input {
display: block;
width: 100%;
margin-top: 4px;
}
/* A sense redrawn from the change that is queued for it, or a whole new sense waiting in the list's own
numbering. No badge and no frame: the point is that it reads the way it will read once saved. The tint
is the only sign, and the queue box is what actually says nothing is published yet. */
.dsb-sense-preview {
background: var(--wikt-accent-tint);
box-shadow: -3px 0 0 var(--wikt-accent-edge);
}
/* Kept in the tree so another gadget's form still has somewhere to open, but out of sight while the
preview above draws the same content. */
.dsb-preview-hidden {
display: none !important;
}
li.dsb-sense-preview {
display: list-item;
padding-left: 4px;
}
/* </nowiki> */
bhx1ci1ftl0cg2eywc872gosx4zkcqd
سنور
0
1359304
6534900
2026-09-20T14:21:46Z
Wikihez
7344
[[Bikarhêner:Wikihez/templateScript2.js|TS]]: Ji [[en:سنور|en.wikt]]
6534900
wikitext
text/x-wiki
== {{ziman|ar}} ==
=== Bilêvkirin ===
{{ar-pr|سِنَّوْر}}
=== Navdêr ===
[[Wêne:Wild Cat at Kanha.gif|thumb|{{lang|ar|سِنَّوْر}}|upright=1.36]]
{{navdêr|ar|سِنَّوْر|c=n|p=سَنَانِير}}
# [[pisîk]], [[kitik]]
==== Formên alternatîv ====
* {{hw|ar|سُنَّار|شُنَارَى}}
== {{ziman|ota}} ==
=== Navdêr ===
{{navdêr|ota|tr=sınor, sinor, sınır}}
# [[hidûd]], [[sînor]]
#: {{hevmane|ota|حدود|tr1=hudud}}
# [[lîmît]]
==== Etîmolojî ====
Ji {{mîr|trk-oat|ota|سِنُورْ}}, ji {{ji|el|ota|σύνορον}}.<ref>{{R:tr:Nişanyan|sınır}}</ref><ref>{{R:ota:Sami:1899|page=739}}</ref>
==== Dûnde ====
* {{dû|tr|sınır}}
* {{dû|hy|սինոր|սինոռ|սնոր|սնոռ|deyn=1}}
* {{dû|rup|sinurŭ|deyn=1}}
* {{dû|bg|си́нор|deyn=1}}
* {{dû|mk|синор|deyn=1}}
* {{dû|sh|sìnor//сѝнор|deyn=1}}
=== Çavkanî ===
{{çavkanî}}
==== Bêhtir bixwîne====
* {{R:ota:Meninski|page=2690}}
== {{ziman|trk-oat}} ==
=== Navdêr ===
{{navdêr|trk-oat|سِنُورْ|tr=sinor, sınor}}
# [[hidûd]], [[sînor]]
# [[lîmît]]
==== Formên alternatîv ====
* {{hw|trk-oat|tr=sīnūr|سِینُورْ }}
==== Etîmolojî ====
Ji {{deyn|grc|trk-oat|σύνορον}}.<ref>{{R:tr:Nişanyan|sınır}}</ref><ref>{{R:ota:Sami:1899|page=739}}</ref>
==== Dûnde ====
* {{dû|az|sınır}}
* {{dû|gag|sınır}}
* {{dardû|ota|سنور}}
=== Çavkanî ===
{{çavkanî}}
5ltx38bz4t4232qj5flcl2tlukd14uj
6534912
6534900
2026-09-20T14:44:23Z
Wikihez
7344
/* Çavkanî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6534912
wikitext
text/x-wiki
== {{ziman|ar}} ==
=== Bilêvkirin ===
{{ar-pr|سِنَّوْر}}
=== Navdêr ===
[[Wêne:Wild Cat at Kanha.gif|thumb|{{lang|ar|سِنَّوْر}}|upright=1.36]]
{{navdêr|ar|سِنَّوْر|c=n|p=سَنَانِير}}
# [[pisîk]], [[kitik]]
==== Formên alternatîv ====
* {{hw|ar|سُنَّار|شُنَارَى}}
== {{ziman|ota}} ==
=== Navdêr ===
{{navdêr|ota|tr=sınor, sinor, sınır}}
# [[hidûd]], [[sînor]]
#: {{hevmane|ota|حدود|tr1=hudud}}
# [[lîmît]]
==== Etîmolojî ====
Ji {{mîr|trk-oat|ota|سِنُورْ}}, ji {{ji|el|ota|σύνορον}}.<ref>{{R:tr:Nişanyan|sınır}}</ref><ref>{{R:ota:Sami:1899|page=739}}</ref>
==== Dûnde ====
* {{dû|tr|sınır}}
* {{dû|hy|սինոր|սինոռ|սնոր|սնոռ|deyn=1}}
* {{dû|rup|sinurŭ|deyn=1}}
* {{dû|bg|си́нор|deyn=1}}
* {{dû|mk|синор|deyn=1}}
* {{dû|sh|sìnor//сѝнор|deyn=1}}
==== Çavkanî ====
{{çavkanî}}
==== Bêhtir bixwîne====
* {{R:ota:Meninski|page=2690}}
== {{ziman|trk-oat}} ==
=== Navdêr ===
{{navdêr|trk-oat|سِنُورْ|tr=sinor, sınor}}
# [[hidûd]], [[sînor]]
# [[lîmît]]
==== Formên alternatîv ====
* {{hw|trk-oat|tr=sīnūr|سِینُورْ }}
==== Etîmolojî ====
Ji {{deyn|grc|trk-oat|σύνορον}}.<ref>{{R:tr:Nişanyan|sınır}}</ref><ref>{{R:ota:Sami:1899|page=739}}</ref>
==== Dûnde ====
* {{dû|az|sınır}}
* {{dû|gag|sınır}}
* {{dardû|ota|سنور}}
=== Çavkanî ===
{{çavkanî}}
tap7a86m5jg09yuz9gs30dteuqtrgax
6534913
6534912
2026-09-20T14:44:42Z
Wikihez
7344
/* Çavkanî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6534913
wikitext
text/x-wiki
== {{ziman|ar}} ==
=== Bilêvkirin ===
{{ar-pr|سِنَّوْر}}
=== Navdêr ===
[[Wêne:Wild Cat at Kanha.gif|thumb|{{lang|ar|سِنَّوْر}}|upright=1.36]]
{{navdêr|ar|سِنَّوْر|c=n|p=سَنَانِير}}
# [[pisîk]], [[kitik]]
==== Formên alternatîv ====
* {{hw|ar|سُنَّار|شُنَارَى}}
== {{ziman|ota}} ==
=== Navdêr ===
{{navdêr|ota|tr=sınor, sinor, sınır}}
# [[hidûd]], [[sînor]]
#: {{hevmane|ota|حدود|tr1=hudud}}
# [[lîmît]]
==== Etîmolojî ====
Ji {{mîr|trk-oat|ota|سِنُورْ}}, ji {{ji|el|ota|σύνορον}}.<ref>{{R:tr:Nişanyan|sınır}}</ref><ref>{{R:ota:Sami:1899|page=739}}</ref>
==== Dûnde ====
* {{dû|tr|sınır}}
* {{dû|hy|սինոր|սինոռ|սնոր|սնոռ|deyn=1}}
* {{dû|rup|sinurŭ|deyn=1}}
* {{dû|bg|си́нор|deyn=1}}
* {{dû|mk|синор|deyn=1}}
* {{dû|sh|sìnor//сѝнор|deyn=1}}
==== Çavkanî ====
{{çavkanî}}
==== Bêhtir bixwîne====
* {{R:ota:Meninski|page=2690}}
== {{ziman|trk-oat}} ==
=== Navdêr ===
{{navdêr|trk-oat|سِنُورْ|tr=sinor, sınor}}
# [[hidûd]], [[sînor]]
# [[lîmît]]
==== Formên alternatîv ====
* {{hw|trk-oat|tr=sīnūr|سِینُورْ }}
==== Etîmolojî ====
Ji {{deyn|grc|trk-oat|σύνορον}}.<ref>{{R:tr:Nişanyan|sınır}}</ref><ref>{{R:ota:Sami:1899|page=739}}</ref>
==== Dûnde ====
* {{dû|az|sınır}}
* {{dû|gag|sınır}}
* {{dardû|ota|سنور}}
==== Çavkanî ====
{{çavkanî}}
b93li4d7zn0j7wsppsce1h0l06loyzw
6534915
6534913
2026-09-20T15:41:07Z
Balyozbot
7867
[[Special:PermanentLink/6298023|Bot]]: rêzkirina beşan
6534915
wikitext
text/x-wiki
== {{ziman|ar}} ==
=== Bilêvkirin ===
{{ar-pr|سِنَّوْر}}
=== Navdêr ===
[[Wêne:Wild Cat at Kanha.gif|thumb|{{lang|ar|سِنَّوْر}}|upright=1.36]]
{{navdêr|ar|سِنَّوْر|c=n|p=سَنَانِير}}
# [[pisîk]], [[kitik]]
==== Formên alternatîv ====
* {{hw|ar|سُنَّار|شُنَارَى}}
== {{ziman|ota}} ==
=== Navdêr ===
{{navdêr|ota|tr=sınor, sinor, sınır}}
# [[hidûd]], [[sînor]]
#: {{hevmane|ota|حدود|tr1=hudud}}
# [[lîmît]]
==== Etîmolojî ====
Ji {{mîr|trk-oat|ota|سِنُورْ}}, ji {{ji|el|ota|σύνορον}}.<ref>{{R:tr:Nişanyan|sınır}}</ref><ref>{{R:ota:Sami:1899|page=739}}</ref>
==== Dûnde ====
* {{dû|tr|sınır}}
* {{dû|hy|սինոր|սինոռ|սնոր|սնոռ|deyn=1}}
* {{dû|rup|sinurŭ|deyn=1}}
* {{dû|bg|си́нор|deyn=1}}
* {{dû|mk|синор|deyn=1}}
* {{dû|sh|sìnor//сѝнор|deyn=1}}
==== Bêhtir bixwîne====
* {{R:ota:Meninski|page=2690}}
==== Çavkanî ====
{{çavkanî}}
== {{ziman|trk-oat}} ==
=== Navdêr ===
{{navdêr|trk-oat|سِنُورْ|tr=sinor, sınor}}
# [[hidûd]], [[sînor]]
# [[lîmît]]
==== Formên alternatîv ====
* {{hw|trk-oat|tr=sīnūr|سِینُورْ }}
==== Etîmolojî ====
Ji {{deyn|grc|trk-oat|σύνορον}}.<ref>{{R:tr:Nişanyan|sınır}}</ref><ref>{{R:ota:Sami:1899|page=739}}</ref>
==== Dûnde ====
* {{dû|az|sınır}}
* {{dû|gag|sınır}}
* {{dardû|ota|سنور}}
==== Çavkanî ====
{{çavkanî}}
eeyq6k4qfsphuse5vbwfbubw827u619
Şablon:Ç:ota:Sami:1899
10
1359305
6534901
2026-09-20T14:22:02Z
Wikihez
7344
[[:en:Template:R:ota:Sami:1899]] hat kopîkirin ([[Bikarhêner:Wikihez/skrîpt/js/kopi-enWikt.js|kopi-enWikt.js]])
6534901
wikitext
text/x-wiki
{{#invoke:quote|call_template
|template=cite-book
|ota|author=Şemseddin Sâmi|authorlink=Sami Frashëri|year=1899–1901|title={{lang|ota|قاموس تركی}} [kamus-ı türki]|location=Constantinople|publisher=İkdam Matbaası|url={{#if:{{{1|}}}||https://archive.org/details/kamusitrki00emse}}
|pageurl={{#if:{{{1|}}}|https://archive.org/details/kamusitrki00emse/page/n{{#expr:{{#invoke:string/templates|match|{{{1}}}|[0-9]+}}+3}}/mode/1up?view=theater}}
|page={{{page|{{#if:{{{to|}}}||{{{1|}}}}}}}}
|pages={{{pages|{{#if:{{{to|}}}|{{{1}}}–{{{to}}}}}}}}
|entry={{#if:{{{1|}}}|{{#ifeq:{{{2|}}}|-||{{lang|ota|{{{2|{{pagename}}}}}}}}}}}
|allowparams=1,to,2,page,pages
}}<noinclude>{{documentation}}{{reference template cat|ota}}</noinclude>
a5zgewl9s8imbkkxhagquk4f7mip8bj
6534903
6534901
2026-09-20T14:22:16Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:R:ota:Sami:1899]] weke [[Şablon:Ç:ota:Sami:1899]] guhart
6534901
wikitext
text/x-wiki
{{#invoke:quote|call_template
|template=cite-book
|ota|author=Şemseddin Sâmi|authorlink=Sami Frashëri|year=1899–1901|title={{lang|ota|قاموس تركی}} [kamus-ı türki]|location=Constantinople|publisher=İkdam Matbaası|url={{#if:{{{1|}}}||https://archive.org/details/kamusitrki00emse}}
|pageurl={{#if:{{{1|}}}|https://archive.org/details/kamusitrki00emse/page/n{{#expr:{{#invoke:string/templates|match|{{{1}}}|[0-9]+}}+3}}/mode/1up?view=theater}}
|page={{{page|{{#if:{{{to|}}}||{{{1|}}}}}}}}
|pages={{{pages|{{#if:{{{to|}}}|{{{1}}}–{{{to}}}}}}}}
|entry={{#if:{{{1|}}}|{{#ifeq:{{{2|}}}|-||{{lang|ota|{{{2|{{pagename}}}}}}}}}}}
|allowparams=1,to,2,page,pages
}}<noinclude>{{documentation}}{{reference template cat|ota}}</noinclude>
a5zgewl9s8imbkkxhagquk4f7mip8bj
Şablon:Ç:ota:Sami:1899/belge
10
1359306
6534902
2026-09-20T14:22:03Z
Wikihez
7344
[[:en:Template:R:ota:Sami:1899/documentation]] hat kopîkirin ([[Bikarhêner:Wikihez/skrîpt/js/kopi-enWikt.js|kopi-enWikt.js]])
6534902
wikitext
text/x-wiki
{{documentation subpage}}
; {{para|1|opt=1}}
: The page number, in Western digits, ranging from <code>3</code> to <code>1580</code>. This will make the link go to that specific page.
; {{para|2|opt=1}}
: The name of the entry in the dictionary. This will not affect the link.
: This defaults to the page name. To suppress the title, input <code>-</code>.
; {{para|to|opt=1}}
: The last page of a page range, where {{para|1}} is the first one. This will not affect the link either.
eectcrg2qd86xflpy3ut8bu7rcblkuo
6534905
6534902
2026-09-20T14:22:16Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:R:ota:Sami:1899/belge]] weke [[Şablon:Ç:ota:Sami:1899/belge]] guhart
6534902
wikitext
text/x-wiki
{{documentation subpage}}
; {{para|1|opt=1}}
: The page number, in Western digits, ranging from <code>3</code> to <code>1580</code>. This will make the link go to that specific page.
; {{para|2|opt=1}}
: The name of the entry in the dictionary. This will not affect the link.
: This defaults to the page name. To suppress the title, input <code>-</code>.
; {{para|to|opt=1}}
: The last page of a page range, where {{para|1}} is the first one. This will not affect the link either.
eectcrg2qd86xflpy3ut8bu7rcblkuo
Şablon:R:ota:Sami:1899
10
1359307
6534904
2026-09-20T14:22:16Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:R:ota:Sami:1899]] weke [[Şablon:Ç:ota:Sami:1899]] guhart
6534904
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:Ç:ota:Sami:1899]]
hwndfsjt1uu0c58da5897x2yfctmboq
Şablon:R:ota:Sami:1899/belge
10
1359308
6534906
2026-09-20T14:22:16Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:R:ota:Sami:1899/belge]] weke [[Şablon:Ç:ota:Sami:1899/belge]] guhart
6534906
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:Ç:ota:Sami:1899/belge]]
ct83lb69rz6vnzxfwa34m7otiodr1d3
Şablon:J:Feqiyê Teyran:2022
10
1359309
6534928
2026-09-20T17:53:00Z
Wikihez
7344
Rûpel bi "{{#invoke:quote|call_quote_template |ku |sal=~1620 |isbn=9789751750808 |nivîskar=w:Feqiyê Teyran |edîtor=w:Kadri Yıldırım |sernav=Dîwan |weşanxane=Wezareta Kultur û Turîzmê ya Tirkiyeyê |çap=2 |sala-weşandinê=2022 |cih=w:Enqere |textparam=jg,jêgirtin |pageparam=r,rûpel }}<!-- -->{{kategorî|Jêgirtinên ji Feqiyê Teyran}}<!-- -->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude> <noinclude>{{belgekirin|naverok= == Bikaranîn ==..." hat çêkirin
6534928
wikitext
text/x-wiki
{{#invoke:quote|call_quote_template
|ku
|sal=~1620
|isbn=9789751750808
|nivîskar=w:Feqiyê Teyran
|edîtor=w:Kadri Yıldırım
|sernav=Dîwan
|weşanxane=Wezareta Kultur û Turîzmê ya Tirkiyeyê
|çap=2
|sala-weşandinê=2022
|cih=w:Enqere
|textparam=jg,jêgirtin
|pageparam=r,rûpel
}}<!--
-->{{kategorî|Jêgirtinên ji Feqiyê Teyran}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
<noinclude>{{belgekirin|naverok=
== Bikaranîn ==
<syntaxhighlight lang="wikitext" copy>
{{J:Feqiyê Teyran:2022|jêgirtin= |rûpel= }}
</syntaxhighlight>
== TemplateData ==
<templatedata>
{
"params": {
"jêgirtin": {
"aliases": [
"jg"
]
},
"rûpel": {
"aliases": [
"r"
]
}
},
"format": "inline",
"description": "Ji bo jêgirtinên ji Feqiyê Teyran, edîtoriya Kadri Yıldırım, Dîwan, çapa 2an, Enqere: Wezareta Kultur û Turîzmê ya Tirkiyeyê, 2022"
}
</templatedata>
{{quotecat|ku}}
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Feqiyê Teyran]]
}}
== Herwiha binêre ==
* {{ş|J:Feqiyê Teyran:2013}}
</noinclude>
khkg5h7af43zaplv44k6bax3l41e33k
bi rê birin
0
1359310
6535028
2026-09-20T21:32:10Z
~2026-50815-48
15385
Rûpel bi "== {{ziman|ku}} == === Lêker === {{lêker|ku|c=gh}} # {{bnr|bi rê ve birin}}" hat çêkirin
6535028
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Lêker ===
{{lêker|ku|c=gh}}
# {{bnr|bi rê ve birin}}
qjiun2w01vitkvfceoa1z8f5wjd0dg8
κύτος
0
1359311
6535034
2026-09-20T22:54:19Z
Wikihez
7344
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +grc:navdêr:xulole; her cure aman wekî cer an vazo
6535034
wikitext
text/x-wiki
== {{ziman|grc}} ==
=== Navdêr ===
{{navdêr|grc|c=nt}}
# [[xulole|xulole]]
# Her [[cure|cure]] [[aman|aman]] [[wekî|wekî]] [[cer|cer]] an [[vazo|vazo]].
==== Etîmolojî ====
{{etîmon|grc|:dariştî<unc>|ine-pro:*(s)kewH-<t:çerm>|metn=++}}
fxynf4ohz0bzdpwdkg32q7uq9w6osvs
6535037
6535034
2026-09-21T00:12:10Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6535037
wikitext
text/x-wiki
== {{ziman|grc}} ==
=== Navdêr ===
{{navdêr|grc|c=nt}}
# [[xulole]]
# Her [[cure]] [[aman]] [[wekî]] [[cer]] an [[vazo]].
==== Etîmolojî ====
{{etîmon|grc|:dariştî<unc>|ine-pro:*(s)kewH-<t:çerm>|metn=++}}
ffinyps81q9r0idf5ir50ehoe3a18f6
crown anemone
0
1359312
6535042
2026-09-21T08:42:24Z
Kurdistanov
15290
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +en:navdêr:adarok
6535042
wikitext
text/x-wiki
== {{ziman|en}} ==
=== Navdêr ===
{{navdêr|en}}
# [[adarok]], [[gulalesûre]], [[kurikmast]]
jhursql82jjqmll4lt7hml76unggns8
anémone de Caen
0
1359313
6535043
2026-09-21T08:45:48Z
Kurdistanov
15290
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +fr:navdêr:adarok, gulalesûre, kurikmast
6535043
wikitext
text/x-wiki
== {{ziman|fr}} ==
=== Navdêr ===
{{navdêr|fr|c=m}}
# [[adarok]], [[gulalesûre]], [[kurikmast]]
86j9bxzkap4j110oad9uz9unb7wx71f
anémone couronnée
0
1359314
6535044
2026-09-21T08:47:57Z
Kurdistanov
15290
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +fr:navdêr:adarok, gulalesûre, kurikmast
6535044
wikitext
text/x-wiki
== {{ziman|fr}} ==
=== Navdêr ===
{{navdêr|fr|c=m}}
# [[adarok]], [[gulalesûre]], [[kurikmast]]
86j9bxzkap4j110oad9uz9unb7wx71f
Anemone coronaria
0
1359315
6535045
2026-09-21T08:52:06Z
Kurdistanov
15290
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +fr:navdêr:adarok, gulalesûre, kurikmast
6535045
wikitext
text/x-wiki
== {{ziman|fr}} ==
=== Navdêr ===
{{navdêr|fr|c=m}}
# [[adarok]], [[gulalesûre]], [[kurikmast]]
86j9bxzkap4j110oad9uz9unb7wx71f
6535046
6535045
2026-09-21T08:52:35Z
Kurdistanov
15290
6535046
wikitext
text/x-wiki
== {{ziman|la}} ==
=== Navdêr ===
{{navdêr|la|c=m}}
# [[adarok]], [[gulalesûre]], [[kurikmast]]
71r339ycaud41hdn7wx9iv0dh7sk41h
Kronen-Anemone
0
1359316
6535047
2026-09-21T08:54:01Z
Kurdistanov
15290
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +de:navdêr:adarok, gulalesûre, kurikmast
6535047
wikitext
text/x-wiki
== {{ziman|de}} ==
=== Navdêr ===
{{navdêr|de|c=m}}
# [[adarok]], [[gulalesûre]], [[kurikmast]]
sqdveygcd3rldl8yk9wm914067fnz1r
Blauer Eisenhut
0
1359317
6535048
2026-09-21T08:58:35Z
Kurdistanov
15290
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +de:navdêr:gurxenêq, kerxenêq
6535048
wikitext
text/x-wiki
== {{ziman|de}} ==
=== Navdêr ===
{{navdêr|de|c=n}}
# [[gurxenêq]], [[kerxenêq]]
l3ho7dlc3no8qortgs1rj1n1zz16gqt
ox-eye daisy
0
1359318
6535052
2026-09-21T09:13:19Z
Kurdistanov
15290
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +en:navdêr:beybûna mêrgan
6535052
wikitext
text/x-wiki
== {{ziman|en}} ==
=== Navdêr ===
{{navdêr|en}}
# [[beybûna mêrgan]]
swdnmk7m6rfex9250pw2j0redg79af2
6535054
6535052
2026-09-21T09:22:46Z
Kurdistanov
15290
6535054
wikitext
text/x-wiki
== {{ziman|en}} ==
=== Navdêr ===
{{navdêr|en}}
# [[beybûna mêrgan]]
=== Herwiha ===
* [[oxeye daisy]]
ik3l2s20d0ondmfkylh5mup1r6wmgw5
oxeye daisy
0
1359319
6535055
2026-09-21T09:28:39Z
~2026-50881-86
15390
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +en:navdêr:beybûna mêrgan
6535055
wikitext
text/x-wiki
== {{ziman|en}} ==
=== Navdêr ===
{{navdêr|en}}
# [[beybûna mêrgan]]
=== Herwiha ===
* [[ox-eye daisy]]
mcyrls91ji1xbe5xwy5rmuqd3zxw6c5
marguerite de prés
0
1359320
6535056
2026-09-21T09:30:56Z
~2026-50881-86
15390
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +fr:navdêr:[[beybûna mêrgan]]
6535056
wikitext
text/x-wiki
== {{ziman|fr}} ==
=== Navdêr ===
{{navdêr|fr|c=m}}
# [[beybûna mêrgan]]
5zju76sxotjs62slh7kvtbjzioy59pz
çayır papatyası
0
1359321
6535057
2026-09-21T09:33:06Z
~2026-50881-86
15390
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +tr:navdêr:beybûna mêrgan
6535057
wikitext
text/x-wiki
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# [[beybûna mêrgan]]
qcdfsqr9ey9l9j1f0ezi1q4fhkf7d54
Leucanthemum vulgare
0
1359322
6535058
2026-09-21T09:34:26Z
~2026-50881-86
15390
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +la:navdêr:beybûna mêrgan
6535058
wikitext
text/x-wiki
== {{ziman|la}} ==
=== Navdêr ===
{{navdêr|la|c=nt}}
# [[beybûna mêrgan]]
onut5piwfecqaz4899aqvd1fdhdglwy
Wiesen-Margerite
0
1359323
6535059
2026-09-21T09:37:54Z
Kurdistanov
15290
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +de:navdêr:beybûna mêrgan
6535059
wikitext
text/x-wiki
== {{ziman|de}} ==
=== Navdêr ===
{{navdêr|de|c=m}}
# [[beybûna mêrgan]]
69aknxc6v45ddmyo6vcgtmxc71kq71e
tirincok
0
1359324
6535067
2026-09-21T11:00:11Z
Kurdistanov
15290
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +ku:navdêr:Riwekek peldar e û pelên wê wek sûrat tên bikaranîn
6535067
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|c=m}}
# [[riwek|Riwekek]] [[peldar]] e û [[pel|pelên]] wê wek [[sûrat]] tên [[bikaranîn]].
==== Etîmolojî ====
{{ji?|ku}}
==== Bi zaravayên din ====
{{werger-ser|riwekek peldar e û pelên wê wek sûrat tên bikaranîn|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|riwekek peldar e û pelên wê wek sûrat tên bikaranîn|1}}
{{werger-bin}}
bvxn4vrjnrd6e1kxdtzbfjjxhw22haj
6535068
6535067
2026-09-21T11:05:38Z
Kurdistanov
15290
Werger: sernav: "tirincok"; sernav: "tirincok"; +îngilîzî : [[lemon balm]]; +fransî : [[mélisse]]; +fransî : [[mélisse citronée]]; +tirkî : [[ofiulotu]]; +latînî : [[Melissa officinalis]] (bi alîkarî)
6535068
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|c=m}}
# [[riwek|Riwekek]] [[peldar]] e û [[pel|pelên]] wê wek [[sûrat]] tên [[bikaranîn]].
==== Etîmolojî ====
{{ji?|ku}}
==== Bi zaravayên din ====
{{werger-ser|tirincok}}
{{werger-bin}}
==== Werger ====
{{werger-ser|tirincok}}
* {{Z|fr}}: {{W+|fr|mélisse|m}}, {{W-|fr|mélisse citronée|m}}
* {{Z|en}}: {{W+|en|lemon balm}}
* {{Z|la}}: {{W-|la|Melissa officinalis}}
* {{Z|tr}}: {{W-|tr|ofiulotu}}
{{werger-bin}}
08382q17jr7tvfq85qk7b36m0rcr3aq
6535069
6535068
2026-09-21T11:08:40Z
Kurdistanov
15290
/* Werger */
6535069
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|c=m}}
# [[riwek|Riwekek]] [[peldar]] e û [[pel|pelên]] wê wek [[sûrat]] tên [[bikaranîn]].
==== Etîmolojî ====
{{ji?|ku}}
==== Bi zaravayên din ====
{{werger-ser|tirincok}}
{{werger-bin}}
==== Werger ====
{{werger-ser|tirincok}}
* {{Z|fr}}: {{W+|fr|mélisse|m}}, {{W-|fr|mélisse citronée|m}}
* {{Z|en}}: {{W+|en|lemon balm}}
* {{Z|la}}: {{W-|la|Melissa officinalis}}
* {{Z|tr}}: {{W+|tr|oğulotu}}
{{werger-bin}}
d35z3bfzsdj0n3t5lv7o49jg1akima8
doxteşî
0
1359325
6535070
2026-09-21T11:12:33Z
Kurdistanov
15290
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +ku:navdêr:dûlkê teşiyê
6535070
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|c=m}}
# [[dûlkê teşiyê]].
==== Etîmolojî ====
{{ji?|ku}}
==== Bi zaravayên din ====
{{werger-ser|dûlkê teşiyê|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|dûlkê teşiyê|1}}
{{werger-bin}}
ewzpchlk40zlhbl7rcevttz7mlwqrjy
6535072
6535070
2026-09-21T11:13:33Z
Kurdistanov
15290
Werger: +îngilîzî : [[spindle's stem]] (bi alîkarî)
6535072
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|c=m}}
# [[dûlkê teşiyê]].
==== Etîmolojî ====
{{ji?|ku}}
==== Bi zaravayên din ====
{{werger-ser|dûlkê teşiyê|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|dûlkê teşiyê|1}}
* {{Z|en}}: {{W-|en|spindle's stem}}
{{werger-bin}}
9ciycu3vcv9jymjinf232v75sbpm6m1
teşîrêsî
0
1359326
6535074
2026-09-21T11:19:37Z
Kurdistanov
15290
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +ku:lêker:gerandin, bi taybetî gerandina teşiyê
6535074
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Lêker ===
{{lêker|ku}}
# [[gerandin|Gerandin]], bi taybetî gerandina [[teşî|teşiyê]].
==== Etîmolojî ====
[[teşî]] + [[rês]] + [[-î]]
==== Bi zaravayên din ====
{{werger-ser|gerandin, bi taybetî gerandina teşiyê|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|gerandin, bi taybetî gerandina teşiyê|1}}
{{werger-bin}}
kgy2ty14r0u3td1qsxgg2yvozm7t3bo