ویکیواژه
fawiktionary
https://fa.wiktionary.org/wiki/%D9%88%DB%8C%DA%A9%DB%8C%E2%80%8C%D9%88%D8%A7%DA%98%D9%87:%D8%B5%D9%81%D8%AD%D9%87%D9%94_%D8%A7%D8%B5%D9%84%DB%8C
MediaWiki 1.47.0-wmf.13
case-sensitive
مدیا
ویژه
بحث
کاربر
بحث کاربر
ویکیواژه
بحث ویکیواژه
پرونده
بحث پرونده
مدیاویکی
بحث مدیاویکی
الگو
بحث الگو
راهنما
بحث راهنما
رده
بحث رده
پیوست
بحث پیوست
TimedText
TimedText talk
پودمان
بحث پودمان
Event
Event talk
آتابک
0
3851
909021
892457
2026-07-31T03:41:47Z
Kunakes
46856
آتابک/ویرایش، اصلاح رده و حذف فرهنگ مندرآوردی «فرهنگ شمس»
909021
wikitext
text/x-wiki
==فارسی==
===گونههای دیگر نوشتاری===
*[[اتابک]]
===ریشه لغت===
*{{ترکی}}
===آوایش===
*/آتا/بَک/
===[[file:Nuvola apps bookcase2.svg|32px]] اسم مرکب===
'''آتابک''' {{قدیم}}
#در دوره [[قاجار]]، لقبی که به وزیران داده میشد←[[اتابک]]
#لقب هر یک از پادشاهان مستقل که حکومتهای محلی داشتند.
#کسی که پرورش فرزندان پادشاه و بزرگان را برعهده داشت.
#:پدربزرگ، وزیربزرگ، یا بزرگ قوم و قبیله مانند [[آتاترک]] و همچنین [[اتاليق]] يعنی پدر خوانده.<ref>چميرزا علیاصغر خان اتابک اعظم (۱۲۷۵-۱۳۲۵ ه. ق.) پسر آقا ابراهيم امينالسلطان صدراعظم ناصرالدينشاه و مظفر الدين شاه و محمحد عليشاه . در بيست و يکم رجب سنه ۱۳۲۵ ه. ق. شب هنگامی که از [[مجلس شورای ملی]] خارج میشد بدست مردی از اهالی آذربايجان موسوم به عباسآقا (عضو انجمن آذربايجان) در سن پنجاه سالگی به قتل رسيد</ref>
#{{قدیم}}: [[لهله|لهَلهَ]] مؤدب مربی کودک، بویژه مربی شاهزادگان.
{{-}}
{{ترجمه-بالا}}
*{{انگلیسی}}: [[grandparent]]
{{ترجمه-پایین}}
====منابع====
*فرهنگ بزرگ سخن
====پانویس====
{{پانویس}}
[[رده:اسمهای مرکب ترکی]]
[[رده:اسمهای مرکب آذری]]
[[رده:اصطلاحات با پیشوند آتا]]
[[رده:اصطلاحات با پسوند بک]]
[[En:grandparent]]
5k3kisn7tti9h95t549r4w0y0223m26
آپاردی
0
5275
909020
892942
2026-07-31T03:29:47Z
Kunakes
46856
آپاردی/اصلاح جزئی
909020
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{ترکی}}
===گونههای دیگر نوشتاری===
*[[آپارده]]
===آوایش===
*/آپاردِی/
===[[file:Nuvola apps bookcase2.svg|32px]] صفت ===
'''آپاردی'''
#(گفتگو): بُردن، ربودن، دزدیدن؛ [[آپارتی]].
#:تلویحا جوجه [[اردک]] برکه دهکده برقآسا توسط [[شغال]] یا روباهی ربوده شدن.
{{-}}
{{top}}
*{{انگلیسی}}:[[swindler]]
{{bottom}}
====منابع====
* فرهنگ لغت معین
[[رده:صفتهای ترکی]]
[[رده:صفت]]
[[رده:گفتگو]]
[[رده:توهینآمیز]]
[[En:swindler]]
8mz1u00ncbjk4hx3c1wway0urkq0gor
آتی
0
5322
909028
892665
2026-07-31T04:18:16Z
Kunakes
46856
آتی/اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909028
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{فارسی}}
===آوایش===
*/آتِی/
===[[file:Nuvola apps bookcase2.svg|35px]] صفت===
'''آتی'''
#آینده، آنکه پس از این آید. [[آتيه]]، [[آينده]].
#:آتی ممکن در [[زبان معیار باستان]] به دو بخش [[آ]] - [[طی]] قابل تجزیه باشد، و نشانه شرط یا مشروط است.
{{-}}
{{ترجمه-بالا}}
*{{انگليسی}}:[[coming]]
*[[future]]
*[[following]]
*[[prognostic]]
{{ترجمه-پایین}}
====منابع====
*فرهنگ لغت معین/ فرهنگ بزرگ سخن
[[رده:صفت]]
[[رده:اسمهای فارسی]]
[[رده:اسمهای عربی]]
[[رده:اصطلاحات با پیشوند آ]]
[[رده:اصطلاحات با پسوند تی]]
7wuft2bh8o17stnu40918bojy5ateay
آجال
0
5329
909030
892612
2026-07-31T04:27:21Z
Kunakes
46856
آجال/اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909030
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{عربی}}
===آوایش===
*/آجال/
===[[file:Nuvola apps bookcase2.svg|32px]] اسم===
'''آجال'''
#{{قدیم}}: جمعِ [[اجل]]؛ وقتهای معیّن، مهلتها. مرگها.
#:آجال ممکن است در [[زبان معیار باستان]] به دو بخش [[آج]] - [[آل]] قابل تجزیه باشد و به معنی ایل گرسنه یا ایل گرسنگان بوده است.
====منابع====
*فرهنگ بزرگ سخن
[[رده:اسمهای عربی]]
[[رده:اسمهای فارسی]]
[[رده:کهن واژه]]
[[رده:زبان معیار باستان]]
[[رده:اصطلاحات با پیشوند آج]]
[[رده:اصطلاحات با پسوند آل]]
i1jo0yk3kp8pykvhkyug9mnbluzyk7a
آجام
0
14388
909031
865710
2026-07-31T04:31:15Z
Kunakes
46856
آجام/اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909031
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{عربی}}
===آوایش===
*/آجام/
===[[file:Nuvola apps bookcase2.png|32px]] اسم===
'''آجام'''
#{{قدیم}}: بیشهها، جنگلها.
#:آجام در [[زبان معیار باستان]] ممکن است به دو بخش [[آج]] - [[ آم]] قابل تجزیه باشد و مفهوم از آن یعنی زن گرسنه، یا زنان گرسنه بوده است.
#جمع الجمع اَجَمه بمعنی نيستان و نيزار وبيشه (لغتنامهٔ دهخدا)
#جمع اُجْم و اُجُم بمعنی حصار و حصن و دژ و دز
{{-}}
{{ترجمه-بالا}}
*{{انگلیسی}}: [[brushwood]]
*[[forest]]
*[[grove]]
*[[citadel]]
*[[stronghold]]
*[[fortress]]
{{ترجمه-پایین}}
====منابع====
*فرهنگ بزرگ سخن
[[رده:اسمهای عربی]]
[[رده:کهن واژه]]
[[رده:زبان معیار باستان]]
[[رده:اصطلاحات با پیشوند آج]]
[[رده:اصطلاحات با پسوند آم]]
[[En:brushwood]]
6bp0030pmtxjl3gqe6j2pk37gbdb3n2
آچماخ
0
23413
909042
896432
2026-07-31T06:12:30Z
Kunakes
46856
آ
909042
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{آذری}}
===آوایش===
*/آچ/ماخ/
===[[file:Nuvola apps bookcase2.png|30px]] فعل===
'''آچماخ'''
#[[باز کردن]]، گشودن به ویژه گرههای سخت و کور الیاف و نخهای درهم تنیده. [[آچماق]].
{{-}}
{{ترجمه-بالا}}
*{{انگلیسی}}: [[opening]]
{{ترجمه-پایین}}
[[رده:اسمهای ترکی]]
[[رده:اسمهای آذری]]
[[رده:واژههای آذری]]
[[رده:اصطلاحات با پیشوند آچ]]
[[رده:اصطلاحات با پسوند ماخ]]
[[En:opening]]
n5fvr3oxqlhpqvdtfrmcybe6qxny1gx
909043
909042
2026-07-31T06:14:43Z
Kunakes
46856
آچماخ/اصلاح، تفاوت گویش ترکی و آذری با زبان بهاریِ← آچماق
909043
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{ترکی}}
===آوایش===
*/آچ/ماخ/
===[[file:Nuvola apps bookcase2.png|30px]] فعل===
'''آچماخ'''
#[[باز کردن]]، گشودن به ویژه گرههای سخت و کور الیاف و نخهای درهم تنیده. [[آچماق]].
{{-}}
{{ترجمه-بالا}}
*{{انگلیسی}}: [[opening]]
{{ترجمه-پایین}}
[[رده:اسمهای ترکی]]
[[رده:اسمهای آذری]]
[[رده:واژههای آذری]]
[[رده:اصطلاحات با پیشوند آچ]]
[[رده:اصطلاحات با پسوند ماخ]]
[[En:opening]]
0xvav21ftk76kfeg5ea7728bb41cfw1
کره شمالی
0
27667
909044
851639
2026-07-31T06:19:27Z
Saĝaĉjo
13896
اصلاح غلط تایپی «کُ رِ شُخخخخخخخخ» به «کُ رِ شُ»
909044
wikitext
text/x-wiki
==فارسی==
===آوایش===
# کُ رِ شُ
===اسم خاص===
# '''کره شمالی'''
* [[کره]] + [[شمالی]]
# [[کشور]]ی در خاور آسیا که در همسایگی کشورهای [[چین]] و [[کره جنوبی]] جای دارد. پایتخت این کشور [[پیونگ یانگ]] است.
{{ویکیپدیا}}
[[رده:واژههای فارسی]]
[[رده:اسمهای فارسی]]
[[رده:اسمهای خاص فارسی]]
[[رده:کشور]]
d7bgeo4ba4cex5pt7lzrm7806utcnkt
آتل
0
31373
909026
896424
2026-07-31T04:05:35Z
Kunakes
46856
آتل/اصلاح
909026
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{گیرش|بهاری|آتِل}}
===آوایش===
*/آتِل/
===[[file:Nuvola apps bookcase2.png|40px]] اسم===
'''آتل'''
#{{پز}}: قطعه محکمی برای بیحرکت ساختن قسمتی از بدن که در مواردی مانند شکستگی، دررفتگی، و آسیبهای دیگر، دور [[عضو]] میبندند.
#ابزاری برای ثابت نگه داشتن اندام شکسته بدن.
{{-}}
{{ترجمه-بالا}}
*{{انگلیسی}}: [[brace]]
{{ترجمه-پایین}}
====منابع====
*فرهنگ لغت معین/ فرهنگ بزرگ سخن
[[رده:اسمهای فرانسوی]]
[[رده:اسمهای فرانسوی]]
[[رده:فارسی-فرانسوی]]
[[En:brace]]
ie89h0bv3a6ocn0rezqdig028pl3j0b
آجان
0
31383
909032
898683
2026-07-31T04:34:38Z
Kunakes
46856
آجان/اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909032
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*فارسی
{{ویکیپدیا|آجان (زرندیه)}}
===آوایش===
*/آجان/
===[[file:Nuvola apps bookcase2.png|32px]] اسم===
'''آجان'''
#{{گفتگو}}: مأمور [[شهربانی]]، باسپان، نیروی انتظامی، [[آژان]].
#:آجان در [[زبان معیار باستان]] ممکن است به دو بخش [[آج]] - [[آن]] قابل تجزیه باشد و تلویحا به معنی دائم گرسنه بوده است. گرسنه پول، انعام یا پاداش.
#روستایی از توابع بخش [[خرقان]] شهرستان [[زرندیه]] استان مرکزی.
====منابع====
*فرهنگ بزرگ سخن
[[رده:اسمهای فرانسوی]]
[[رده:اسمهای فارسی-فرانسوی]]
[[رده:کهن واژه]]
[[رده:عامیانه]]
[[رده:اسمهای فارسی]]
[[رده:روستاها]]
[[رده:زبان معیار باستان]]
[[رده:اصطلاحات با پیشوند آج]]
[[رده:اصطلاحات با پسوند آن]]
60nv63fu4rcsx17qkpvxrhy7pkr4mdp
آجل
0
31384
909035
865846
2026-07-31T05:31:17Z
Kunakes
46856
آجل/ویرایش، اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909035
wikitext
text/x-wiki
==فارسی==
===گونههای دیگر نوشتاری===
*[[عاجل]]
===ریشه لغت===
*{{عربی}}
===آوایش===
*/آجُل/---/آجَل/
===[[file:Nuvola apps bookcase2.png|30px]] اسم===
'''آجل'''
#[[آروغ]].
===[[file:Nuvola apps bookcase2.png|30px]] صفت===
'''آجِل'''
#{{قدیم}}: مربوط به زمان [[آینده]]، آتی. مقابل [[عاجل]]. آخرت. مدتدار.
#:آجل یا عاجل در [[زبان معیار باستان]]، چه در تلفظ و چه در معنا هیچ تفاوتی باهم نداشتند، و از دو بخش [[آج]] - [[ائل]] به معنی ایل قحطیزده و بَدَوی بودهاست؛ که با حضور غیره منتظره پیرامون شهرها یا دهکدهها موجب بروز قحطی میشدند.
===واژههای مشتق شده===
*[[آجلا]]
{{-}}
{{ترجمه-بالا}}
*{{انگلیسی}}: [[ultimate]]
{{ترجمه-پایین}}
====منابع====
*فرهنگ لغت معین/ فرهنگ بزرگ سخن
[[رده:صفتهای عربی]]
[[رده:صفت]]
[[رده:کهن واژه]]
[[رده:گویش بهاری]]
[[رده:زبان معیار باستان]]
[[رده:اصطلاحات با پیشوند آج]]
[[رده:اصطلاحات با پسوند ائل]]
[[En:ultimate]]
6j380idlwqlmi7ljwks1zoztagzkfks
آچ
0
31388
909040
896434
2026-07-31T06:05:32Z
Kunakes
46856
آچ/اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909040
wikitext
text/x-wiki
==فارسی==
===ریشه شناسی===
*{{ترکی}}
===آوایش===
*/آچ/
===[[file:Nuvola apps bookcase2.png|30px]] اسم===
'''آچ'''
#[[افرا]].
#مترادف مضارع [[گشودن]] در فارسی، [[بازکن]].
===واژههای مشتق شده===
*[[آچار]]، [[آچمز]]
{{-}}
{{ترجمه-بالا}}
*{{انگلیسی}}: [[maple]]
{{ترجمه-پایین}}
====منابع====
*فرهنگ لغت معین
[[رده:اسمهای آذری]]
[[رده:اسمهای ترکی]]
[[رده:گویش بهاری]]
[[رده:اصطلاحات با پیشوند آچ]]
[[رده:اصطلاحات با پسوند آچ]]
mhhxxpdbta3ql4jn74n9ii2wem937el
بابی
0
31969
909045
600998
2026-07-31T06:22:16Z
Saĝaĉjo
13896
اصلاح واژهی «فرقه» که بار معنایی منفی و قضاوتگر علیه اقلیتهای دینی داره به «آیین»
909045
wikitext
text/x-wiki
==فارسی==
(ص نسب.)
# منسوب به سید علی محمد باب، از آیین بابیه.
====منابع====
* فرهنگ لغت معین
[[رده:فارسی]]
====برگردانها====
;{{انگلیسی}}: [[triforium]]
*[[pell mell]]
*[[foolhardily]]
*[[sea green]]
*[[petunia]]
*[[sass]]
mtuq5c1oxvlv3lncrmqmj02cplx5nly
تکاب
0
36190
909046
871888
2026-07-31T06:23:31Z
Kunakes
46856
تکاب/اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909046
wikitext
text/x-wiki
==فارسی==
[[File:IMG E3647(1).jpg|بندانگشتی|245px|چپ|محوطه باستانی تختسلیمان در شهرستان تکاب]]
===ریشه لغت===
*{{آذری}}
{{ویکیپدیا|تکاب}}
===آوایش===
*/تَکاب/
===[[تصویر:Nuvola apps bookcase2.png|30px]] اسم===
'''تکاب'''
#{{قدیم}}: آبکند، [[باتلاق]]، زمینی که آب در آن بماند. ته آب، قعر آب، دره. آب، رود، یا دریای عمیق.
#جنگ، نبرد.
#از شهرهای باستانی ایران واقع در استان [[آذربایجان غربی]] که سابقه تمدنی آن به اواسط هزاره سوم پیش از میلاد برمیگردد.
#:اسامی زیادی به این تمدن ایرانی در طول تاریخ اطلاق شده؛ [[شیز]]، [[گنزک]]، [[تخت سلیمان]] و مهمتر از همه آتشکده یا کعبه [[آذرگشسپ]].
{{-}}
{{ترجمه-بالا}}
*{{انگلیسی}}: [[takab]]
{{ترجمه-پایین}}
====منابع====
*فرهنگ بزرگ سخن/ فرهنگ لغت معین
[[رده:اسمهای ترکی]]
[[رده:اسمهای آذری]]
[[رده:گویش بهاری]]
[[رده:کهن وازه]]
[[رده:حوزه نفوذ فرهنگ آذرگشسپ]]
[[رده:اصطلاحات با پیشوند تک]]
[[رده:اصطلاحات با پسوند آب]]
[[رده:شهرها]]
[[رده:استان آذربایجان غربی]]
[[En:takab]]
8td6slz2mqidi273g9in0p6k4zhqsiw
909047
909046
2026-07-31T06:30:12Z
Kunakes
46856
تکاب/ویرایش جزئی
909047
wikitext
text/x-wiki
==فارسی==
[[File:IMG E3647(1).jpg|بندانگشتی|245px|چپ|محوطه باستانی تختسلیمان در شهرستان تکاب]]
===ریشه لغت===
*{{آذری}}
{{ویکیپدیا|تکاب}}
===آوایش===
*/تَکاب/
===[[تصویر:Nuvola apps bookcase2.png|30px]] اسم===
'''تکاب'''
#{{قدیم}}: آبکند، [[باتلاق]]، زمینی که آب در آن بماند. ته آب، قعر آب، دره. آب، رود، یا دریای عمیق.
#جنگ، نبرد.
#از شهرهای باستانی ایران واقع در استان [[آذربایجان غربی]] که سابقه تمدنی آن به اواسط هزاره سوم پیش از میلاد برمیگردد.
#:عناوین مختلفی به این تمدن ایرانی در طول تاریخ اطلاق شده← [[شیز]]، [[گنزک]]، [[تخت سلیمان]] و مهمتر از همه آتشکده (کعبه) [[آذرگشسپ]]، اما در زبان و گویشهای محلی از تکاب تحت عنوان [[تیکاب]] و [[تیکان تپه]] یاد میشود.
{{-}}
{{ترجمه-بالا}}
*{{انگلیسی}}: [[takab]]
{{ترجمه-پایین}}
====منابع====
*فرهنگ بزرگ سخن/ فرهنگ لغت معین
[[رده:اسمهای ترکی]]
[[رده:اسمهای آذری]]
[[رده:گویش بهاری]]
[[رده:کهن وازه]]
[[رده:حوزه نفوذ فرهنگ آذرگشسپ]]
[[رده:اصطلاحات با پیشوند تک]]
[[رده:اصطلاحات با پسوند آب]]
[[رده:شهرها]]
[[رده:استان آذربایجان غربی]]
[[En:takab]]
r7ceazjt1ioqlx07ntunq91n8rdph3x
909048
909047
2026-07-31T06:32:04Z
Kunakes
46856
تکاب/اصلاح رده
909048
wikitext
text/x-wiki
==فارسی==
[[File:IMG E3647(1).jpg|بندانگشتی|245px|چپ|محوطه باستانی تختسلیمان در شهرستان تکاب]]
===ریشه لغت===
*{{آذری}}
{{ویکیپدیا|تکاب}}
===آوایش===
*/تَکاب/
===[[تصویر:Nuvola apps bookcase2.png|30px]] اسم===
'''تکاب'''
#{{قدیم}}: آبکند، [[باتلاق]]، زمینی که آب در آن بماند. ته آب، قعر آب، دره. آب، رود، یا دریای عمیق.
#جنگ، نبرد.
#از شهرهای باستانی ایران واقع در استان [[آذربایجان غربی]] که سابقه تمدنی آن به اواسط هزاره سوم پیش از میلاد برمیگردد.
#:عناوین مختلفی به این تمدن ایرانی در طول تاریخ اطلاق شده← [[شیز]]، [[گنزک]]، [[تخت سلیمان]] و مهمتر از همه آتشکده (کعبه) [[آذرگشسپ]]، اما در زبان و گویشهای محلی از تکاب تحت عنوان [[تیکاب]] و [[تیکان تپه]] یاد میشود.
{{-}}
{{ترجمه-بالا}}
*{{انگلیسی}}: [[takab]]
{{ترجمه-پایین}}
====منابع====
*فرهنگ بزرگ سخن/ فرهنگ لغت معین
[[رده:اسمهای ترکی]]
[[رده:اسمهای آذری]]
[[رده:گویش بهاری]]
[[رده:کهن واژه]]
[[رده:حوزه نفوذ فرهنگ آذرگشسپ]]
[[رده:اصطلاحات با پیشوند تک]]
[[رده:اصطلاحات با پسوند آب]]
[[رده:شهرها]]
[[رده:استان آذربایجان غربی]]
[[En:takab]]
tufjt8asbcg587djs37wud4bfkpf40j
عاجل
0
51450
909037
813147
2026-07-31T05:47:53Z
Kunakes
46856
عاجِل/ویرایش
909037
wikitext
text/x-wiki
==فارسی==
===گونههای دیگر نوشتاری===
*[[آجل]]
===ریشه لغت===
*{{عربی}}
===آوایش===
*/عاجِل/
===[[file:Nuvola apps bookcase2.png|30px]] صفت===
'''عاجل'''
#شتابان، شتاب کننده، تند، سریع. و به مجاز، به سرعت انجام شونده، [[زودرس]].
#{{قدیم}}: مربوط به زمان حال، فعلی، مقابل [[آجل]].
#{{مجاز}}: اکنون، زمان حال، مقابل [[آجل]].
# این جهان، دنیا.
===واژههای مشتق شده===
*[[عاجلاً]]
{{-}}
{{ترجمه-بالا}}
*{{انگلیسی}}: [[prompt]]
*[[pressing]]
{{ترجمه-پایین}}
====منابع====
*فرهنگ لغت معین/ فرهنگ بزرگ سخن
[[رده:صفت]]
[[رده:صفتها]]
[[رده:صفتهای عربی]]
5mss1gy2mw212tkl0i0mo6s56p3mhbo
عاجلاً
0
51453
909038
614068
2026-07-31T05:52:33Z
Kunakes
46856
عاجلاً/ویرایش
909038
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{عربی}}
===آوایش===
*/عاجلَنْ/
===[[file:Nuvola apps bookcase2.png|30px]] قید===
'''عاجلا'''
# فوری، به سرعت. خیلی زود، [[بیدرنگ]].
====منابع====
* فرهنگ لغت معین/ فرهنگ بزرگ سخن
[[رده:قید]]
[[رده:قیدها]]
[[رده:قیدهای عربی]]
04zi2vjpbsqs55tqe2h7nc7l8v0re2f
تله تايپ
0
84241
909049
325421
2026-07-31T11:33:26Z
EmausBot
19188
اصلاح تغییرمسیر دوتایی به [[تلهتایپ]]
909049
wikitext
text/x-wiki
#تغییر_مسیر [[تلهتایپ]]
37593xbngqwq6ybi6iz9ua42hmex78p
بحث کاربر:Renamed user bdb6e17a99c4560f10cf09a6ba5a38f2
3
159904
909015
817909
2026-07-30T22:02:30Z
Aelxen
46873
Aelxen صفحهٔ [[بحث کاربر:C8rbon]] را بدون برجایگذاشتن تغییرمسیر به [[بحث کاربر:Renamed user bdb6e17a99c4560f10cf09a6ba5a38f2]] منتقل کرد: صفحه در ضمن تغییر نام کاربر «[[Special:CentralAuth/C8rbon|C8rbon]]» به «[[Special:CentralAuth/Renamed user bdb6e17a99c4560f10cf09a6ba5a38f2|Renamed user bdb6e17a99c4560f10cf09a6ba5a38f2]]» به طور خودکار منتقل شد
761049
wikitext
text/x-wiki
== خوش آمدید ==
[[ویکیواژه:تازهواردان، خوش آمدید|'''به ویکیواژه خوشآمدید''']]!<br />
امیدوارم حضوری طولانی مدت در ویکیواژه داشته باشید و بتوانیم با همکاری یکدیگر در جهت پیشرفت هر چه بیشتر این وبگاه گام برداریم. ویکیواژه منتظر نوشتارهای خوب شماست.<br />
این پیوندها میتواند سودمند باشد:
{|
|-
|[[پرونده:Noia 64 apps help index.png|30px|right|link=ویکیواژه:راهنما|راهنما]] || [[ویکیواژه:راهنما|راهنما]] برای راهنمایی گرفتن
|-
| [[پرونده:Nuvola apps ksig.png|30px|right|link=ویکیواژه:آموزش سریع|آموزش سریع]] || [[ویکیواژه:آموزش سریع|آموزش سریع]]
|-
|||[[راهنما:افزودن سرواژه نو|افزودن سرواژهٔ نو]] راهنمای افزودن سرواژهٔ نو
|-
|||[[ویکیواژه:شیوهنامه|شیوهنامه]] شیوهنامهٔ ویکیواژه
|-
|[[File:Bucket in the sand.svg|right|50px]]||[[ویکیواژه:صفحه تمرین|صفحه تمرین]] برای تمرین ویرایش
|-
| [[پرونده:Nuvola apps chat.png|30px|right|link=ویکیواژه:قهوهخانه|قهوهخانه]]||[[ویکیواژه:قهوهخانه|قهوهخانه]] برای گفتگو دربارهٔ مسائل فنی و سیاستها.
|-
|[[پرونده:Farsi.svg|30px|right|link=ویکیواژه:فارسینویسی|فارسینویسی]]||[[ویکیواژه:فارسینویسی|فارسینویسی]]
|-
|[[پرونده:Nuvola apps agent.svg|30px|right|link=w:ویکیپدیا:خودآموز|آموزش پیشرفته]]||[[w:ویکیپدیا:خودآموز|آموزش پیشرفته]]
|-
|
|-
|
|-
|'''پروژههای دیگر بنیاد'''||[[پرونده:Wikipedia-logo.png|25px|link=w:صفحه_اصلی|ویکیپدیا]][[پرونده:HSWikimedia.svg|30px|link=m:Special:Recentchanges|فراویکی]] [[پرونده:HSCommons.svg|30px|link=commons:Special:Recentchanges|ویکیانبار]][[پرونده:HSWNews.svg|30px|link=n:ویژه:تغییرات اخیر|ویکیخبر]] [[پرونده:HSWBooks.svg|30px|link=b:صفحه_اصلی|ویکینَسک]] [[پرونده:HSWVoyage.svg|30px|link=voy:صفحه_اصلی|ویکیسفر]][[پرونده:HSWQuote.svg|30px|link=q:صفحه_اصلی|ویکیگفتاورد]][[پرونده:HSWSource.svg|30px|link=s:صفحه_اصلی|ویکینبشته]]
|}
امیدوارم از ''[[ویکیواژه:ویکیواژه نویسان|ویکیواژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~~~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکیواژه:قهوهخانه|قهوهخانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد میگویم!شاد باشید!
-- [[کاربر:AFlorence|'''فلورانس''']] ([[بحث کاربر:AFlorence|بحث]]) ۲۱ اوت ۲۰۱۷، ساعت ۱۵:۲۴ (UTC)
8thnqta2v361hgb0hcnl5waoepipqki
geben
0
177876
909014
789688
2026-07-30T19:08:07Z
~2026-42328-82
46872
/* */
909014
wikitext
text/x-wiki
Geben
==آلمانی==
===فعل===
{{فعل-آل}}
# [[دادن]]
midrqqnkwcof8a5u76g6fdwylll0jrb
آتلانتیس
0
178385
909023
899255
2026-07-31T03:53:56Z
Kunakes
46856
آتلانتیس/اصلاح ردهها و حذف منبع من درآوردی «فرهنگ شمس»
909023
wikitext
text/x-wiki
==فارسی==
[[پرونده:Danby-deluge.jpg|بندانگشتی|245px|چپ|پرترهای از حادثه آتلانتیس]]
===ریشه لغت===
*{{گیرش|بهاری|آتدانیس}}
{{ویکیپدیا|آتلانتیس}}
===آوایش===
*/آتلانتیس/
===[[file:Nuvola apps bookcase2.svg|35px]] اسمخاص===
'''آتلانتیس''' {{نامباستانی}}
#جزیرهای ناشناخته در آثار افلاطون. [[آتلانتیک]].
#:این واژه ممکن است اشاره به واقعه [[طوفاننوح]] از زبان یا منبع دیگر باشد.
{{-}}
{{ترجمه-بالا}}
*{{انگلیسی}}: [[atlantis]]
*{{یونانی باستان}}: [[Ἀτλαντὶς]]
{{ترجمه-پایین}}
[[رده:کهن واژه]]
[[رده:زبان معیار باستان]]
[[رده:حوزه نفوذ فرهنگ آذرگشسپ]]
[[رده:اسمهای یونانی باستان]]
[[رده:اسمهای خاص یونانی]]
[[رده:اصطلاحات با پیشوند آت]]
[[En:atlantis]]
nb0ks75601xvfrxssq0sdtsjtyi75fa
بحث کاربر:Mamouri
3
192045
908982
908966
2026-07-30T15:49:15Z
Mamouri
34239
/* درخواست کمک */ پاسخ
908982
wikitext
text/x-wiki
== خوش آمدید ==
[[ویکیواژه:تازهواردان، خوش آمدید|'''به ویکیواژه خوشآمدید''']]!<br />
امیدوارم حضوری طولانی مدت در ویکیواژه داشته باشید و بتوانیم با همکاری یکدیگر در جهت پیشرفت هر چه بیشتر این وبگاه گام برداریم. ویکیواژه منتظر نوشتارهای خوب شماست.<br />
این پیوندها میتواند سودمند باشد:
{|
|-
|[[پرونده:Noia 64 apps help index.png|30px|right|link=ویکیواژه:راهنما|راهنما]] || [[ویکیواژه:راهنما|راهنما]] برای راهنمایی گرفتن
|-
| [[پرونده:Nuvola apps ksig.png|30px|right|link=ویکیواژه:آموزش سریع|آموزش سریع]] || [[ویکیواژه:آموزش سریع|آموزش سریع]]
|-
|||[[راهنما:افزودن سرواژه نو|افزودن سرواژهٔ نو]] راهنمای افزودن سرواژهٔ نو
|-
|||[[ویکیواژه:شیوهنامه|شیوهنامه]] شیوهنامهٔ ویکیواژه
|-
|[[File:Bucket in the sand.svg|right|50px]]||[[ویکیواژه:صفحه تمرین|صفحه تمرین]] برای تمرین ویرایش
|-
| [[پرونده:Nuvola apps chat.png|30px|right|link=ویکیواژه:قهوهخانه|قهوهخانه]]||[[ویکیواژه:قهوهخانه|قهوهخانه]] برای گفتگو دربارهٔ مسائل فنی و سیاستها.
|-
|[[پرونده:Farsi.svg|30px|right|link=ویکیواژه:فارسینویسی|فارسینویسی]]||[[ویکیواژه:فارسینویسی|فارسینویسی]]
|-
|[[پرونده:Nuvola apps agent.svg|30px|right|link=w:ویکیپدیا:خودآموز|آموزش پیشرفته]]||[[w:ویکیپدیا:خودآموز|آموزش پیشرفته]]
|-
|
|-
|
|-
|'''پروژههای دیگر بنیاد'''||[[پرونده:Wikipedia-logo.png|25px|link=w:صفحه_اصلی|ویکیپدیا]][[پرونده:HSWikimedia.svg|30px|link=m:Special:Recentchanges|فراویکی]] [[پرونده:HSCommons.svg|30px|link=commons:Special:Recentchanges|ویکیانبار]][[پرونده:HSWNews.svg|30px|link=n:ویژه:تغییرات اخیر|ویکیخبر]] [[پرونده:HSWBooks.svg|30px|link=b:صفحه_اصلی|ویکینَسک]] [[پرونده:HSWVoyage.svg|30px|link=voy:صفحه_اصلی|ویکیسفر]][[پرونده:HSWQuote.svg|30px|link=q:صفحه_اصلی|ویکیگفتاورد]][[پرونده:HSWSource.svg|30px|link=s:صفحه_اصلی|ویکینبشته]]
|}
امیدوارم از ''[[ویکیواژه:ویکیواژه نویسان|ویکیواژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~~~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکیواژه:قهوهخانه|قهوهخانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد میگویم! شاد باشید!
-- [[کاربر:AFlorence|'''فلورانس''']] ([[بحث کاربر:AFlorence|بحث]]) ۱۴ مه ۲۰۲۲، ساعت ۰۶:۴۹ (UTC)
== درخواست کمک ==
درود. وقت بخیر. شما که دومین رأی موافق را به مدیریت من را دادید و برای کنش در ویکیواژه هم ابراز علاقه نمودید، لطفاً یک کمک با رباتتان به من میدهید؟ حجم خرابکاری بسیار بزرگ و درازمدتی در اینجا هست که باید به کمک یک ربات که پشتش کاربری درستکار هست زدوده شود. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۱۳ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۴۲ (UTC)
:@[[کاربر:The.shahab|The.shahab]] گرامی؛ بسیار خرسندم که به من پیام دادید و پوزش میخواهم که این قدر دیر پاسخ میدهم. فکر خیلی خوبی است و فکر میکنم میشود سامانه شهباز را که در ویکیپدیای فارسی هست را به کار برد. این روزها کمی دسترسیام به اینترنت کم است. به زودی تحقیق میکنم و راهکار مناسب را پیدا میکنم و خبرتان میکنم. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۱۹ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۵۴ (UTC)
:@[[کاربر:The.shahab|The.shahab]] گرامی؛ با پوزش از تاخیر، [[ویکیواژه:ویکیواژهٔ فارسی، تبعیدگاه خرابکاران ویکیپدیای فارسی؟|انشای شما]] را خواندم. خواستم بگویم که تنها نیستید و این را یادم نرفته است و به زودی تمام انرژیم را به ویکیواژه منتقل میکنم که با کمک و همراهی شما اینجا را سر و سامان دهیم. حتما به زودی خبرتان میکنم. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۰۲:۰۷ (UTC)
::@[[کاربر:Mamouri|Mamouri]] سلامت باشید. در اولین فرصت، لطفاً ابزار توئینکل را در ویکیواژه بومیسازی کنید. دهها سرواژه هست که آماده برای حذفند. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۰۸:۱۱ (UTC)
::@[[کاربر:The.shahab|The.shahab]] گرامی؛ کار را آغاز کردم. پروژهٔ خیلی خیلی بزرگی است و احتمالا یک هفتهای به درازا بیانجامد. نیاز به تصمیمگیریهایی هم هست که بهتر است شما بگیرید. من فعلاً برای این که بتوان کار را جلو برد یک پیشفرضهایی را درست میکنم و بعداً با راهنمایی و کمک شما آنها را به شکلی که شما مناسب میبینید تغییر خواهیم داد. [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۵:۴۹ (UTC)
koex7mryrozkc6ejrx6radhkcyxyed1
909010
908982
2026-07-30T16:13:03Z
Mamouri
34239
/* درخواست کمک */ پاسخ
909010
wikitext
text/x-wiki
== خوش آمدید ==
[[ویکیواژه:تازهواردان، خوش آمدید|'''به ویکیواژه خوشآمدید''']]!<br />
امیدوارم حضوری طولانی مدت در ویکیواژه داشته باشید و بتوانیم با همکاری یکدیگر در جهت پیشرفت هر چه بیشتر این وبگاه گام برداریم. ویکیواژه منتظر نوشتارهای خوب شماست.<br />
این پیوندها میتواند سودمند باشد:
{|
|-
|[[پرونده:Noia 64 apps help index.png|30px|right|link=ویکیواژه:راهنما|راهنما]] || [[ویکیواژه:راهنما|راهنما]] برای راهنمایی گرفتن
|-
| [[پرونده:Nuvola apps ksig.png|30px|right|link=ویکیواژه:آموزش سریع|آموزش سریع]] || [[ویکیواژه:آموزش سریع|آموزش سریع]]
|-
|||[[راهنما:افزودن سرواژه نو|افزودن سرواژهٔ نو]] راهنمای افزودن سرواژهٔ نو
|-
|||[[ویکیواژه:شیوهنامه|شیوهنامه]] شیوهنامهٔ ویکیواژه
|-
|[[File:Bucket in the sand.svg|right|50px]]||[[ویکیواژه:صفحه تمرین|صفحه تمرین]] برای تمرین ویرایش
|-
| [[پرونده:Nuvola apps chat.png|30px|right|link=ویکیواژه:قهوهخانه|قهوهخانه]]||[[ویکیواژه:قهوهخانه|قهوهخانه]] برای گفتگو دربارهٔ مسائل فنی و سیاستها.
|-
|[[پرونده:Farsi.svg|30px|right|link=ویکیواژه:فارسینویسی|فارسینویسی]]||[[ویکیواژه:فارسینویسی|فارسینویسی]]
|-
|[[پرونده:Nuvola apps agent.svg|30px|right|link=w:ویکیپدیا:خودآموز|آموزش پیشرفته]]||[[w:ویکیپدیا:خودآموز|آموزش پیشرفته]]
|-
|
|-
|
|-
|'''پروژههای دیگر بنیاد'''||[[پرونده:Wikipedia-logo.png|25px|link=w:صفحه_اصلی|ویکیپدیا]][[پرونده:HSWikimedia.svg|30px|link=m:Special:Recentchanges|فراویکی]] [[پرونده:HSCommons.svg|30px|link=commons:Special:Recentchanges|ویکیانبار]][[پرونده:HSWNews.svg|30px|link=n:ویژه:تغییرات اخیر|ویکیخبر]] [[پرونده:HSWBooks.svg|30px|link=b:صفحه_اصلی|ویکینَسک]] [[پرونده:HSWVoyage.svg|30px|link=voy:صفحه_اصلی|ویکیسفر]][[پرونده:HSWQuote.svg|30px|link=q:صفحه_اصلی|ویکیگفتاورد]][[پرونده:HSWSource.svg|30px|link=s:صفحه_اصلی|ویکینبشته]]
|}
امیدوارم از ''[[ویکیواژه:ویکیواژه نویسان|ویکیواژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~~~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکیواژه:قهوهخانه|قهوهخانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد میگویم! شاد باشید!
-- [[کاربر:AFlorence|'''فلورانس''']] ([[بحث کاربر:AFlorence|بحث]]) ۱۴ مه ۲۰۲۲، ساعت ۰۶:۴۹ (UTC)
== درخواست کمک ==
درود. وقت بخیر. شما که دومین رأی موافق را به مدیریت من را دادید و برای کنش در ویکیواژه هم ابراز علاقه نمودید، لطفاً یک کمک با رباتتان به من میدهید؟ حجم خرابکاری بسیار بزرگ و درازمدتی در اینجا هست که باید به کمک یک ربات که پشتش کاربری درستکار هست زدوده شود. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۱۳ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۴۲ (UTC)
:@[[کاربر:The.shahab|The.shahab]] گرامی؛ بسیار خرسندم که به من پیام دادید و پوزش میخواهم که این قدر دیر پاسخ میدهم. فکر خیلی خوبی است و فکر میکنم میشود سامانه شهباز را که در ویکیپدیای فارسی هست را به کار برد. این روزها کمی دسترسیام به اینترنت کم است. به زودی تحقیق میکنم و راهکار مناسب را پیدا میکنم و خبرتان میکنم. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۱۹ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۵۴ (UTC)
:@[[کاربر:The.shahab|The.shahab]] گرامی؛ با پوزش از تاخیر، [[ویکیواژه:ویکیواژهٔ فارسی، تبعیدگاه خرابکاران ویکیپدیای فارسی؟|انشای شما]] را خواندم. خواستم بگویم که تنها نیستید و این را یادم نرفته است و به زودی تمام انرژیم را به ویکیواژه منتقل میکنم که با کمک و همراهی شما اینجا را سر و سامان دهیم. حتما به زودی خبرتان میکنم. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۰۲:۰۷ (UTC)
::@[[کاربر:Mamouri|Mamouri]] سلامت باشید. در اولین فرصت، لطفاً ابزار توئینکل را در ویکیواژه بومیسازی کنید. دهها سرواژه هست که آماده برای حذفند. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۰۸:۱۱ (UTC)
:::@[[کاربر:The.shahab|The.shahab]] گرامی؛ بومیسازی '''توینکل''' برای ویکیواژه را به پایان رساندم. همهٔ پودمانها را برای معیارها، الگوها و تابلوهای خودِ ویکیوراژه بازنویسی کردم (حذف سریع، پیشنهاد حذف [[کاربر:Mamouri/توینکل]] آمدهاست.
:::اما یک گام پایانی مانده که از دست من برنمیآید: پودمانهای آماده باید در فضای نام «مدیاویکی» (صفحههای <code>مدیاویکی:Gadget-…js</code>) کپی شوند و تعریف ابزار در [[مدیاویکی:Gadgets-definition]] بهروز شود. از سال ۲۰۱۸ ویرایش این صفحههای جاوااسکریپتِ سراسری تنها با دسترسی '''مدیر رابط''' (interface administrator) ممکن است؛ حتی مدیران عادی هم این دسترسی را ندارند و حساب من نیز آن را ندارد.
:::خواهشم این است که این دسترسی به من داده شود تا نصب را انجام دهم. دو راه دارد:
:::* اگر دیوانسالارید، میتوانید از [[ویژه:اختیارات کاربر/Mamouri]] گزینهٔ «مدیر رابط» را برای من فعال کنید.
:::* اگر دادن آن بهصورت محلی در ویکیواژه ممکن نیست، میتوانم در [[m:Steward requests/Permissions]] از متصدیان درخواست بدهم و پشتیبانی شما در آنجا کار را پیش میبرد. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۶:۱۳ (UTC)
::@[[کاربر:The.shahab|The.shahab]] گرامی؛ کار را آغاز کردم. پروژهٔ خیلی خیلی بزرگی است و احتمالا یک هفتهای به درازا بیانجامد. نیاز به تصمیمگیریهایی هم هست که بهتر است شما بگیرید. من فعلاً برای این که بتوان کار را جلو برد یک پیشفرضهایی را درست میکنم و بعداً با راهنمایی و کمک شما آنها را به شکلی که شما مناسب میبینید تغییر خواهیم داد. [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۵:۴۹ (UTC)
6ns6oj2nknmh9up6azvqmntq9oxbqfr
909011
909010
2026-07-30T16:14:13Z
Mamouri
34239
909011
wikitext
text/x-wiki
== خوش آمدید ==
[[ویکیواژه:تازهواردان، خوش آمدید|'''به ویکیواژه خوشآمدید''']]!<br />
امیدوارم حضوری طولانی مدت در ویکیواژه داشته باشید و بتوانیم با همکاری یکدیگر در جهت پیشرفت هر چه بیشتر این وبگاه گام برداریم. ویکیواژه منتظر نوشتارهای خوب شماست.<br />
این پیوندها میتواند سودمند باشد:
{|
|-
|[[پرونده:Noia 64 apps help index.png|30px|right|link=ویکیواژه:راهنما|راهنما]] || [[ویکیواژه:راهنما|راهنما]] برای راهنمایی گرفتن
|-
| [[پرونده:Nuvola apps ksig.png|30px|right|link=ویکیواژه:آموزش سریع|آموزش سریع]] || [[ویکیواژه:آموزش سریع|آموزش سریع]]
|-
|||[[راهنما:افزودن سرواژه نو|افزودن سرواژهٔ نو]] راهنمای افزودن سرواژهٔ نو
|-
|||[[ویکیواژه:شیوهنامه|شیوهنامه]] شیوهنامهٔ ویکیواژه
|-
|[[File:Bucket in the sand.svg|right|50px]]||[[ویکیواژه:صفحه تمرین|صفحه تمرین]] برای تمرین ویرایش
|-
| [[پرونده:Nuvola apps chat.png|30px|right|link=ویکیواژه:قهوهخانه|قهوهخانه]]||[[ویکیواژه:قهوهخانه|قهوهخانه]] برای گفتگو دربارهٔ مسائل فنی و سیاستها.
|-
|[[پرونده:Farsi.svg|30px|right|link=ویکیواژه:فارسینویسی|فارسینویسی]]||[[ویکیواژه:فارسینویسی|فارسینویسی]]
|-
|[[پرونده:Nuvola apps agent.svg|30px|right|link=w:ویکیپدیا:خودآموز|آموزش پیشرفته]]||[[w:ویکیپدیا:خودآموز|آموزش پیشرفته]]
|-
|
|-
|
|-
|'''پروژههای دیگر بنیاد'''||[[پرونده:Wikipedia-logo.png|25px|link=w:صفحه_اصلی|ویکیپدیا]][[پرونده:HSWikimedia.svg|30px|link=m:Special:Recentchanges|فراویکی]] [[پرونده:HSCommons.svg|30px|link=commons:Special:Recentchanges|ویکیانبار]][[پرونده:HSWNews.svg|30px|link=n:ویژه:تغییرات اخیر|ویکیخبر]] [[پرونده:HSWBooks.svg|30px|link=b:صفحه_اصلی|ویکینَسک]] [[پرونده:HSWVoyage.svg|30px|link=voy:صفحه_اصلی|ویکیسفر]][[پرونده:HSWQuote.svg|30px|link=q:صفحه_اصلی|ویکیگفتاورد]][[پرونده:HSWSource.svg|30px|link=s:صفحه_اصلی|ویکینبشته]]
|}
امیدوارم از ''[[ویکیواژه:ویکیواژه نویسان|ویکیواژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~~~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکیواژه:قهوهخانه|قهوهخانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد میگویم! شاد باشید!
-- [[کاربر:AFlorence|'''فلورانس''']] ([[بحث کاربر:AFlorence|بحث]]) ۱۴ مه ۲۰۲۲، ساعت ۰۶:۴۹ (UTC)
== درخواست کمک ==
درود. وقت بخیر. شما که دومین رأی موافق را به مدیریت من را دادید و برای کنش در ویکیواژه هم ابراز علاقه نمودید، لطفاً یک کمک با رباتتان به من میدهید؟ حجم خرابکاری بسیار بزرگ و درازمدتی در اینجا هست که باید به کمک یک ربات که پشتش کاربری درستکار هست زدوده شود. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۱۳ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۴۲ (UTC)
:@[[کاربر:The.shahab|The.shahab]] گرامی؛ بسیار خرسندم که به من پیام دادید و پوزش میخواهم که این قدر دیر پاسخ میدهم. فکر خیلی خوبی است و فکر میکنم میشود سامانه شهباز را که در ویکیپدیای فارسی هست را به کار برد. این روزها کمی دسترسیام به اینترنت کم است. به زودی تحقیق میکنم و راهکار مناسب را پیدا میکنم و خبرتان میکنم. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۱۹ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۵۴ (UTC)
:@[[کاربر:The.shahab|The.shahab]] گرامی؛ با پوزش از تاخیر، [[ویکیواژه:ویکیواژهٔ فارسی، تبعیدگاه خرابکاران ویکیپدیای فارسی؟|انشای شما]] را خواندم. خواستم بگویم که تنها نیستید و این را یادم نرفته است و به زودی تمام انرژیم را به ویکیواژه منتقل میکنم که با کمک و همراهی شما اینجا را سر و سامان دهیم. حتما به زودی خبرتان میکنم. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۰۲:۰۷ (UTC)
::@[[کاربر:Mamouri|Mamouri]] سلامت باشید. در اولین فرصت، لطفاً ابزار توئینکل را در ویکیواژه بومیسازی کنید. دهها سرواژه هست که آماده برای حذفند. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۰۸:۱۱ (UTC)
::@[[کاربر:The.shahab|The.shahab]] گرامی؛ کار را آغاز کردم. پروژهٔ خیلی خیلی بزرگی است و احتمالا یک هفتهای به درازا بیانجامد. نیاز به تصمیمگیریهایی هم هست که بهتر است شما بگیرید. من فعلاً برای این که بتوان کار را جلو برد یک پیشفرضهایی را درست میکنم و بعداً با راهنمایی و کمک شما آنها را به شکلی که شما مناسب میبینید تغییر خواهیم داد. [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۵:۴۹ (UTC)
::@[[کاربر:The.shahab|The.shahab]] گرامی؛ بومیسازی '''توینکل''' برای ویکیواژه را به پایان رساندم. همهٔ پودمانها را برای معیارها، الگوها و تابلوهای خودِ ویکیوراژه بازنویسی کردم (حذف سریع، پیشنهاد حذف [[کاربر:Mamouri/توینکل]] آمدهاست.
::اما یک گام پایانی مانده که از دست من برنمیآید: پودمانهای آماده باید در فضای نام «مدیاویکی» (صفحههای <code>مدیاویکی:Gadget-…js</code>) کپی شوند و تعریف ابزار در [[مدیاویکی:Gadgets-definition]] بهروز شود. از سال ۲۰۱۸ ویرایش این صفحههای جاوااسکریپتِ سراسری تنها با دسترسی '''مدیر رابط''' (interface administrator) ممکن است؛ حتی مدیران عادی هم این دسترسی را ندارند و حساب من نیز آن را ندارد.
::خواهشم این است که این دسترسی به من داده شود تا نصب را انجام دهم. دو راه دارد:
::* اگر دیوانسالارید، میتوانید از [[ویژه:اختیارات کاربر/Mamouri]] گزینهٔ «مدیر رابط» را برای من فعال کنید.
::* اگر دادن آن بهصورت محلی در ویکیواژه ممکن نیست، میتوانم در [[m:Steward requests/Permissions]] از متصدیان درخواست بدهم و پشتیبانی شما در آنجا کار را پیش میبرد. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۶:۱۳ (UTC)
3ydzn60m97vassp13bbsw8egy42fjm2
909012
909011
2026-07-30T17:09:25Z
The.shahab
17835
/* درخواست کمک */ پاسخ
909012
wikitext
text/x-wiki
== خوش آمدید ==
[[ویکیواژه:تازهواردان، خوش آمدید|'''به ویکیواژه خوشآمدید''']]!<br />
امیدوارم حضوری طولانی مدت در ویکیواژه داشته باشید و بتوانیم با همکاری یکدیگر در جهت پیشرفت هر چه بیشتر این وبگاه گام برداریم. ویکیواژه منتظر نوشتارهای خوب شماست.<br />
این پیوندها میتواند سودمند باشد:
{|
|-
|[[پرونده:Noia 64 apps help index.png|30px|right|link=ویکیواژه:راهنما|راهنما]] || [[ویکیواژه:راهنما|راهنما]] برای راهنمایی گرفتن
|-
| [[پرونده:Nuvola apps ksig.png|30px|right|link=ویکیواژه:آموزش سریع|آموزش سریع]] || [[ویکیواژه:آموزش سریع|آموزش سریع]]
|-
|||[[راهنما:افزودن سرواژه نو|افزودن سرواژهٔ نو]] راهنمای افزودن سرواژهٔ نو
|-
|||[[ویکیواژه:شیوهنامه|شیوهنامه]] شیوهنامهٔ ویکیواژه
|-
|[[File:Bucket in the sand.svg|right|50px]]||[[ویکیواژه:صفحه تمرین|صفحه تمرین]] برای تمرین ویرایش
|-
| [[پرونده:Nuvola apps chat.png|30px|right|link=ویکیواژه:قهوهخانه|قهوهخانه]]||[[ویکیواژه:قهوهخانه|قهوهخانه]] برای گفتگو دربارهٔ مسائل فنی و سیاستها.
|-
|[[پرونده:Farsi.svg|30px|right|link=ویکیواژه:فارسینویسی|فارسینویسی]]||[[ویکیواژه:فارسینویسی|فارسینویسی]]
|-
|[[پرونده:Nuvola apps agent.svg|30px|right|link=w:ویکیپدیا:خودآموز|آموزش پیشرفته]]||[[w:ویکیپدیا:خودآموز|آموزش پیشرفته]]
|-
|
|-
|
|-
|'''پروژههای دیگر بنیاد'''||[[پرونده:Wikipedia-logo.png|25px|link=w:صفحه_اصلی|ویکیپدیا]][[پرونده:HSWikimedia.svg|30px|link=m:Special:Recentchanges|فراویکی]] [[پرونده:HSCommons.svg|30px|link=commons:Special:Recentchanges|ویکیانبار]][[پرونده:HSWNews.svg|30px|link=n:ویژه:تغییرات اخیر|ویکیخبر]] [[پرونده:HSWBooks.svg|30px|link=b:صفحه_اصلی|ویکینَسک]] [[پرونده:HSWVoyage.svg|30px|link=voy:صفحه_اصلی|ویکیسفر]][[پرونده:HSWQuote.svg|30px|link=q:صفحه_اصلی|ویکیگفتاورد]][[پرونده:HSWSource.svg|30px|link=s:صفحه_اصلی|ویکینبشته]]
|}
امیدوارم از ''[[ویکیواژه:ویکیواژه نویسان|ویکیواژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~~~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکیواژه:قهوهخانه|قهوهخانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد میگویم! شاد باشید!
-- [[کاربر:AFlorence|'''فلورانس''']] ([[بحث کاربر:AFlorence|بحث]]) ۱۴ مه ۲۰۲۲، ساعت ۰۶:۴۹ (UTC)
== درخواست کمک ==
درود. وقت بخیر. شما که دومین رأی موافق را به مدیریت من را دادید و برای کنش در ویکیواژه هم ابراز علاقه نمودید، لطفاً یک کمک با رباتتان به من میدهید؟ حجم خرابکاری بسیار بزرگ و درازمدتی در اینجا هست که باید به کمک یک ربات که پشتش کاربری درستکار هست زدوده شود. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۱۳ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۴۲ (UTC)
:@[[کاربر:The.shahab|The.shahab]] گرامی؛ بسیار خرسندم که به من پیام دادید و پوزش میخواهم که این قدر دیر پاسخ میدهم. فکر خیلی خوبی است و فکر میکنم میشود سامانه شهباز را که در ویکیپدیای فارسی هست را به کار برد. این روزها کمی دسترسیام به اینترنت کم است. به زودی تحقیق میکنم و راهکار مناسب را پیدا میکنم و خبرتان میکنم. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۱۹ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۵۴ (UTC)
:@[[کاربر:The.shahab|The.shahab]] گرامی؛ با پوزش از تاخیر، [[ویکیواژه:ویکیواژهٔ فارسی، تبعیدگاه خرابکاران ویکیپدیای فارسی؟|انشای شما]] را خواندم. خواستم بگویم که تنها نیستید و این را یادم نرفته است و به زودی تمام انرژیم را به ویکیواژه منتقل میکنم که با کمک و همراهی شما اینجا را سر و سامان دهیم. حتما به زودی خبرتان میکنم. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۰۲:۰۷ (UTC)
::@[[کاربر:Mamouri|Mamouri]] سلامت باشید. در اولین فرصت، لطفاً ابزار توئینکل را در ویکیواژه بومیسازی کنید. دهها سرواژه هست که آماده برای حذفند. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۰۸:۱۱ (UTC)
::@[[کاربر:The.shahab|The.shahab]] گرامی؛ کار را آغاز کردم. پروژهٔ خیلی خیلی بزرگی است و احتمالا یک هفتهای به درازا بیانجامد. نیاز به تصمیمگیریهایی هم هست که بهتر است شما بگیرید. من فعلاً برای این که بتوان کار را جلو برد یک پیشفرضهایی را درست میکنم و بعداً با راهنمایی و کمک شما آنها را به شکلی که شما مناسب میبینید تغییر خواهیم داد. [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۵:۴۹ (UTC)
::@[[کاربر:The.shahab|The.shahab]] گرامی؛ بومیسازی '''توینکل''' برای ویکیواژه را به پایان رساندم. همهٔ پودمانها را برای معیارها، الگوها و تابلوهای خودِ ویکیوراژه بازنویسی کردم (حذف سریع، پیشنهاد حذف [[کاربر:Mamouri/توینکل]] آمدهاست.
::اما یک گام پایانی مانده که از دست من برنمیآید: پودمانهای آماده باید در فضای نام «مدیاویکی» (صفحههای <code>مدیاویکی:Gadget-…js</code>) کپی شوند و تعریف ابزار در [[مدیاویکی:Gadgets-definition]] بهروز شود. از سال ۲۰۱۸ ویرایش این صفحههای جاوااسکریپتِ سراسری تنها با دسترسی '''مدیر رابط''' (interface administrator) ممکن است؛ حتی مدیران عادی هم این دسترسی را ندارند و حساب من نیز آن را ندارد.
::خواهشم این است که این دسترسی به من داده شود تا نصب را انجام دهم. دو راه دارد:
::* اگر دیوانسالارید، میتوانید از [[ویژه:اختیارات کاربر/Mamouri]] گزینهٔ «مدیر رابط» را برای من فعال کنید.
::* اگر دادن آن بهصورت محلی در ویکیواژه ممکن نیست، میتوانم در [[m:Steward requests/Permissions]] از متصدیان درخواست بدهم و پشتیبانی شما در آنجا کار را پیش میبرد. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۶:۱۳ (UTC)
:::@[[کاربر:Mamouri|Mamouri]] گرامی؛ زنده باد! سپاس از اینهمه زحمتی که کشیدید.
:::ویکیواژه فارسی، دیوانسالار نداشته و ندارد. من با اکثریت آرا به صورت موقت (سهماهه) مدیر شدم. دوره بعد، میتوانم بدون برگزاری رأیگیری مدیر رابط هم بشوم. در خصوص شما، من میتوانم دسترسی مدیریتی بهتان بدهم. گمان هم نمیکنم که ویکیبدها بدون انتخابات یا داشتن دسترسی مدیریتی قبلی، به شما دسترسی مدیر رابط بدهند. سری بعد که مدیر رابط هم شدم، اگر شدنی هست، دستورالعمل به من بدهید تا کار را انجام دهم.
:::شما یک ربات هم دارید؛ میتوانم به آن دسترسی مدیریتی بدهم، که با مسئولیت خودتان برخی خرابکاریها را ریشهکن کنید؛ چطور است؟ [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۷:۰۹ (UTC)
g64qhj04cvyi0h9h0iq55uvbvo4i0ls
909013
909012
2026-07-30T17:09:56Z
The.shahab
17835
/* درخواست کمک */
909013
wikitext
text/x-wiki
== خوش آمدید ==
[[ویکیواژه:تازهواردان، خوش آمدید|'''به ویکیواژه خوشآمدید''']]!<br />
امیدوارم حضوری طولانی مدت در ویکیواژه داشته باشید و بتوانیم با همکاری یکدیگر در جهت پیشرفت هر چه بیشتر این وبگاه گام برداریم. ویکیواژه منتظر نوشتارهای خوب شماست.<br />
این پیوندها میتواند سودمند باشد:
{|
|-
|[[پرونده:Noia 64 apps help index.png|30px|right|link=ویکیواژه:راهنما|راهنما]] || [[ویکیواژه:راهنما|راهنما]] برای راهنمایی گرفتن
|-
| [[پرونده:Nuvola apps ksig.png|30px|right|link=ویکیواژه:آموزش سریع|آموزش سریع]] || [[ویکیواژه:آموزش سریع|آموزش سریع]]
|-
|||[[راهنما:افزودن سرواژه نو|افزودن سرواژهٔ نو]] راهنمای افزودن سرواژهٔ نو
|-
|||[[ویکیواژه:شیوهنامه|شیوهنامه]] شیوهنامهٔ ویکیواژه
|-
|[[File:Bucket in the sand.svg|right|50px]]||[[ویکیواژه:صفحه تمرین|صفحه تمرین]] برای تمرین ویرایش
|-
| [[پرونده:Nuvola apps chat.png|30px|right|link=ویکیواژه:قهوهخانه|قهوهخانه]]||[[ویکیواژه:قهوهخانه|قهوهخانه]] برای گفتگو دربارهٔ مسائل فنی و سیاستها.
|-
|[[پرونده:Farsi.svg|30px|right|link=ویکیواژه:فارسینویسی|فارسینویسی]]||[[ویکیواژه:فارسینویسی|فارسینویسی]]
|-
|[[پرونده:Nuvola apps agent.svg|30px|right|link=w:ویکیپدیا:خودآموز|آموزش پیشرفته]]||[[w:ویکیپدیا:خودآموز|آموزش پیشرفته]]
|-
|
|-
|
|-
|'''پروژههای دیگر بنیاد'''||[[پرونده:Wikipedia-logo.png|25px|link=w:صفحه_اصلی|ویکیپدیا]][[پرونده:HSWikimedia.svg|30px|link=m:Special:Recentchanges|فراویکی]] [[پرونده:HSCommons.svg|30px|link=commons:Special:Recentchanges|ویکیانبار]][[پرونده:HSWNews.svg|30px|link=n:ویژه:تغییرات اخیر|ویکیخبر]] [[پرونده:HSWBooks.svg|30px|link=b:صفحه_اصلی|ویکینَسک]] [[پرونده:HSWVoyage.svg|30px|link=voy:صفحه_اصلی|ویکیسفر]][[پرونده:HSWQuote.svg|30px|link=q:صفحه_اصلی|ویکیگفتاورد]][[پرونده:HSWSource.svg|30px|link=s:صفحه_اصلی|ویکینبشته]]
|}
امیدوارم از ''[[ویکیواژه:ویکیواژه نویسان|ویکیواژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~~~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکیواژه:قهوهخانه|قهوهخانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد میگویم! شاد باشید!
-- [[کاربر:AFlorence|'''فلورانس''']] ([[بحث کاربر:AFlorence|بحث]]) ۱۴ مه ۲۰۲۲، ساعت ۰۶:۴۹ (UTC)
== درخواست کمک ==
درود. وقت بخیر. شما که دومین رأی موافق را به مدیریت من را دادید و برای کنش در ویکیواژه هم ابراز علاقه نمودید، لطفاً یک کمک با رباتتان به من میدهید؟ حجم خرابکاری بسیار بزرگ و درازمدتی در اینجا هست که باید به کمک یک ربات که پشتش کاربری درستکار هست زدوده شود. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۱۳ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۴۲ (UTC)
:@[[کاربر:The.shahab|The.shahab]] گرامی؛ بسیار خرسندم که به من پیام دادید و پوزش میخواهم که این قدر دیر پاسخ میدهم. فکر خیلی خوبی است و فکر میکنم میشود سامانه شهباز را که در ویکیپدیای فارسی هست را به کار برد. این روزها کمی دسترسیام به اینترنت کم است. به زودی تحقیق میکنم و راهکار مناسب را پیدا میکنم و خبرتان میکنم. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۱۹ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۵۴ (UTC)
:@[[کاربر:The.shahab|The.shahab]] گرامی؛ با پوزش از تاخیر، [[ویکیواژه:ویکیواژهٔ فارسی، تبعیدگاه خرابکاران ویکیپدیای فارسی؟|انشای شما]] را خواندم. خواستم بگویم که تنها نیستید و این را یادم نرفته است و به زودی تمام انرژیم را به ویکیواژه منتقل میکنم که با کمک و همراهی شما اینجا را سر و سامان دهیم. حتما به زودی خبرتان میکنم. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۰۲:۰۷ (UTC)
::@[[کاربر:Mamouri|Mamouri]] سلامت باشید. در اولین فرصت، لطفاً ابزار توئینکل را در ویکیواژه بومیسازی کنید. دهها سرواژه هست که آماده برای حذفند. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۰۸:۱۱ (UTC)
::@[[کاربر:The.shahab|The.shahab]] گرامی؛ کار را آغاز کردم. پروژهٔ خیلی خیلی بزرگی است و احتمالا یک هفتهای به درازا بیانجامد. نیاز به تصمیمگیریهایی هم هست که بهتر است شما بگیرید. من فعلاً برای این که بتوان کار را جلو برد یک پیشفرضهایی را درست میکنم و بعداً با راهنمایی و کمک شما آنها را به شکلی که شما مناسب میبینید تغییر خواهیم داد. [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۵:۴۹ (UTC)
::@[[کاربر:The.shahab|The.shahab]] گرامی؛ بومیسازی '''توینکل''' برای ویکیواژه را به پایان رساندم. همهٔ پودمانها را برای معیارها، الگوها و تابلوهای خودِ ویکیوراژه بازنویسی کردم (حذف سریع، پیشنهاد حذف [[کاربر:Mamouri/توینکل]] آمدهاست.
::اما یک گام پایانی مانده که از دست من برنمیآید: پودمانهای آماده باید در فضای نام «مدیاویکی» (صفحههای <code>مدیاویکی:Gadget-…js</code>) کپی شوند و تعریف ابزار در [[مدیاویکی:Gadgets-definition]] بهروز شود. از سال ۲۰۱۸ ویرایش این صفحههای جاوااسکریپتِ سراسری تنها با دسترسی '''مدیر رابط''' (interface administrator) ممکن است؛ حتی مدیران عادی هم این دسترسی را ندارند و حساب من نیز آن را ندارد.
::خواهشم این است که این دسترسی به من داده شود تا نصب را انجام دهم. دو راه دارد:
::* اگر دیوانسالارید، میتوانید از [[ویژه:اختیارات کاربر/Mamouri]] گزینهٔ «مدیر رابط» را برای من فعال کنید.
::* اگر دادن آن بهصورت محلی در ویکیواژه ممکن نیست، میتوانم در [[m:Steward requests/Permissions]] از متصدیان درخواست بدهم و پشتیبانی شما در آنجا کار را پیش میبرد. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۶:۱۳ (UTC)
:::@[[کاربر:Mamouri|Mamouri]] گرامی؛ زنده باد! سپاس از اینهمه زحمتی که کشیدید.
:::ویکیواژه فارسی، دیوانسالار نداشته و ندارد. من با اکثریت آرا به صورت موقت (سهماهه) مدیر شدم. دوره بعد، میتوانم با برگزاری رأیگیری دوباره، مدیر رابط هم بشوم. در خصوص شما، من میتوانم دسترسی مدیریتی بهتان بدهم. گمان هم نمیکنم که ویکیبدها بدون انتخابات یا داشتن دسترسی مدیریتی قبلی، به شما دسترسی مدیر رابط بدهند. سری بعد که مدیر رابط هم شدم، اگر شدنی هست، دستورالعمل به من بدهید تا کار را انجام دهم.
:::شما یک ربات هم دارید؛ میتوانم به آن دسترسی مدیریتی بدهم، که با مسئولیت خودتان برخی خرابکاریها را ریشهکن کنید؛ چطور است؟ [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۷:۰۹ (UTC)
6dpbkrc7ecl6rdx0zaskb83gig2teqw
909017
909013
2026-07-31T02:39:10Z
Mamouri
34239
/* درخواست کمک */ پاسخ
909017
wikitext
text/x-wiki
== خوش آمدید ==
[[ویکیواژه:تازهواردان، خوش آمدید|'''به ویکیواژه خوشآمدید''']]!<br />
امیدوارم حضوری طولانی مدت در ویکیواژه داشته باشید و بتوانیم با همکاری یکدیگر در جهت پیشرفت هر چه بیشتر این وبگاه گام برداریم. ویکیواژه منتظر نوشتارهای خوب شماست.<br />
این پیوندها میتواند سودمند باشد:
{|
|-
|[[پرونده:Noia 64 apps help index.png|30px|right|link=ویکیواژه:راهنما|راهنما]] || [[ویکیواژه:راهنما|راهنما]] برای راهنمایی گرفتن
|-
| [[پرونده:Nuvola apps ksig.png|30px|right|link=ویکیواژه:آموزش سریع|آموزش سریع]] || [[ویکیواژه:آموزش سریع|آموزش سریع]]
|-
|||[[راهنما:افزودن سرواژه نو|افزودن سرواژهٔ نو]] راهنمای افزودن سرواژهٔ نو
|-
|||[[ویکیواژه:شیوهنامه|شیوهنامه]] شیوهنامهٔ ویکیواژه
|-
|[[File:Bucket in the sand.svg|right|50px]]||[[ویکیواژه:صفحه تمرین|صفحه تمرین]] برای تمرین ویرایش
|-
| [[پرونده:Nuvola apps chat.png|30px|right|link=ویکیواژه:قهوهخانه|قهوهخانه]]||[[ویکیواژه:قهوهخانه|قهوهخانه]] برای گفتگو دربارهٔ مسائل فنی و سیاستها.
|-
|[[پرونده:Farsi.svg|30px|right|link=ویکیواژه:فارسینویسی|فارسینویسی]]||[[ویکیواژه:فارسینویسی|فارسینویسی]]
|-
|[[پرونده:Nuvola apps agent.svg|30px|right|link=w:ویکیپدیا:خودآموز|آموزش پیشرفته]]||[[w:ویکیپدیا:خودآموز|آموزش پیشرفته]]
|-
|
|-
|
|-
|'''پروژههای دیگر بنیاد'''||[[پرونده:Wikipedia-logo.png|25px|link=w:صفحه_اصلی|ویکیپدیا]][[پرونده:HSWikimedia.svg|30px|link=m:Special:Recentchanges|فراویکی]] [[پرونده:HSCommons.svg|30px|link=commons:Special:Recentchanges|ویکیانبار]][[پرونده:HSWNews.svg|30px|link=n:ویژه:تغییرات اخیر|ویکیخبر]] [[پرونده:HSWBooks.svg|30px|link=b:صفحه_اصلی|ویکینَسک]] [[پرونده:HSWVoyage.svg|30px|link=voy:صفحه_اصلی|ویکیسفر]][[پرونده:HSWQuote.svg|30px|link=q:صفحه_اصلی|ویکیگفتاورد]][[پرونده:HSWSource.svg|30px|link=s:صفحه_اصلی|ویکینبشته]]
|}
امیدوارم از ''[[ویکیواژه:ویکیواژه نویسان|ویکیواژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~~~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکیواژه:قهوهخانه|قهوهخانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد میگویم! شاد باشید!
-- [[کاربر:AFlorence|'''فلورانس''']] ([[بحث کاربر:AFlorence|بحث]]) ۱۴ مه ۲۰۲۲، ساعت ۰۶:۴۹ (UTC)
== درخواست کمک ==
درود. وقت بخیر. شما که دومین رأی موافق را به مدیریت من را دادید و برای کنش در ویکیواژه هم ابراز علاقه نمودید، لطفاً یک کمک با رباتتان به من میدهید؟ حجم خرابکاری بسیار بزرگ و درازمدتی در اینجا هست که باید به کمک یک ربات که پشتش کاربری درستکار هست زدوده شود. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۱۳ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۴۲ (UTC)
:@[[کاربر:The.shahab|The.shahab]] گرامی؛ بسیار خرسندم که به من پیام دادید و پوزش میخواهم که این قدر دیر پاسخ میدهم. فکر خیلی خوبی است و فکر میکنم میشود سامانه شهباز را که در ویکیپدیای فارسی هست را به کار برد. این روزها کمی دسترسیام به اینترنت کم است. به زودی تحقیق میکنم و راهکار مناسب را پیدا میکنم و خبرتان میکنم. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۱۹ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۵۴ (UTC)
:@[[کاربر:The.shahab|The.shahab]] گرامی؛ با پوزش از تاخیر، [[ویکیواژه:ویکیواژهٔ فارسی، تبعیدگاه خرابکاران ویکیپدیای فارسی؟|انشای شما]] را خواندم. خواستم بگویم که تنها نیستید و این را یادم نرفته است و به زودی تمام انرژیم را به ویکیواژه منتقل میکنم که با کمک و همراهی شما اینجا را سر و سامان دهیم. حتما به زودی خبرتان میکنم. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۰۲:۰۷ (UTC)
::@[[کاربر:Mamouri|Mamouri]] سلامت باشید. در اولین فرصت، لطفاً ابزار توئینکل را در ویکیواژه بومیسازی کنید. دهها سرواژه هست که آماده برای حذفند. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۰۸:۱۱ (UTC)
::@[[کاربر:The.shahab|The.shahab]] گرامی؛ کار را آغاز کردم. پروژهٔ خیلی خیلی بزرگی است و احتمالا یک هفتهای به درازا بیانجامد. نیاز به تصمیمگیریهایی هم هست که بهتر است شما بگیرید. من فعلاً برای این که بتوان کار را جلو برد یک پیشفرضهایی را درست میکنم و بعداً با راهنمایی و کمک شما آنها را به شکلی که شما مناسب میبینید تغییر خواهیم داد. [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۵:۴۹ (UTC)
::@[[کاربر:The.shahab|The.shahab]] گرامی؛ بومیسازی '''توینکل''' برای ویکیواژه را به پایان رساندم. همهٔ پودمانها را برای معیارها، الگوها و تابلوهای خودِ ویکیوراژه بازنویسی کردم (حذف سریع، پیشنهاد حذف [[کاربر:Mamouri/توینکل]] آمدهاست.
::اما یک گام پایانی مانده که از دست من برنمیآید: پودمانهای آماده باید در فضای نام «مدیاویکی» (صفحههای <code>مدیاویکی:Gadget-…js</code>) کپی شوند و تعریف ابزار در [[مدیاویکی:Gadgets-definition]] بهروز شود. از سال ۲۰۱۸ ویرایش این صفحههای جاوااسکریپتِ سراسری تنها با دسترسی '''مدیر رابط''' (interface administrator) ممکن است؛ حتی مدیران عادی هم این دسترسی را ندارند و حساب من نیز آن را ندارد.
::خواهشم این است که این دسترسی به من داده شود تا نصب را انجام دهم. دو راه دارد:
::* اگر دیوانسالارید، میتوانید از [[ویژه:اختیارات کاربر/Mamouri]] گزینهٔ «مدیر رابط» را برای من فعال کنید.
::* اگر دادن آن بهصورت محلی در ویکیواژه ممکن نیست، میتوانم در [[m:Steward requests/Permissions]] از متصدیان درخواست بدهم و پشتیبانی شما در آنجا کار را پیش میبرد. با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۶:۱۳ (UTC)
:::@[[کاربر:Mamouri|Mamouri]] گرامی؛ زنده باد! سپاس از اینهمه زحمتی که کشیدید.
:::ویکیواژه فارسی، دیوانسالار نداشته و ندارد. من با اکثریت آرا به صورت موقت (سهماهه) مدیر شدم. دوره بعد، میتوانم با برگزاری رأیگیری دوباره، مدیر رابط هم بشوم. در خصوص شما، من میتوانم دسترسی مدیریتی بهتان بدهم. گمان هم نمیکنم که ویکیبدها بدون انتخابات یا داشتن دسترسی مدیریتی قبلی، به شما دسترسی مدیر رابط بدهند. سری بعد که مدیر رابط هم شدم، اگر شدنی هست، دستورالعمل به من بدهید تا کار را انجام دهم.
:::شما یک ربات هم دارید؛ میتوانم به آن دسترسی مدیریتی بدهم، که با مسئولیت خودتان برخی خرابکاریها را ریشهکن کنید؛ چطور است؟ [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۷:۰۹ (UTC)
:::@[[کاربر:The.shahab|The.shahab]] گرامی؛ خواهش میکنم و خیلی لطف دارید. ما دو کار مختلف برای ویکیواژه نیاز داریم: یک. ابزار توینکل که در حال درست کردن آن هستم. دو. ربات ضد خرابکاری که پس از پایان ابزار توینکل به سراغش خواهم رفت. (البته اگر وقت اجازه دهد یا در اولی به مشکل بر بخورم سراغ آن خواهم رفت) اگر امکانش هست لطفاً به من هم دسترسی مدیریت بدهید. لازم نیست دائمی باشد. وقتی ابزار ساخته شد میتوانید آن را بردارید. البته فکر کنم ممکن است همان طور که گفتید باز هم دسترسیهای لازم را نداشته باشم. ولی حداقل به من امکان میدهد که از ویکیبدها درخواست کنم که من را مدیر رابط کنند. حقیقتاً چون برای خود من هم این کار تازگی دارد و کمی آزمون و خطا و رفت و برگشت دارد فکر نمیکنم ساده است که از شما بخواهم که آن را انجام دهید. پس از این که من را مدیر کردید به ویکیبدها خواهم رفت و درخواست میکن که استنثاء بگذارند و من را مدیر رابط کنند و میگویم که پشتیبانی شما را هم دارم. نظر شما چیست؟ با احترام و سپاس فراوان [[کاربر:Mamouri|Mamouri]] ([[بحث کاربر:Mamouri|بحث]]) ۳۱ ژوئیهٔ ۲۰۲۶، ساعت ۰۲:۳۹ (UTC)
559fdf32f4le01w2tsl20dyc1dvw6wm
آپارده
0
200742
909019
865113
2026-07-31T03:26:14Z
Kunakes
46856
آپارده/اصلاح ردهها و حذف منبع مندرآوردی «فرهنگ شمس»
909019
wikitext
text/x-wiki
==فارسی==
===گونههای دیگر نوشتاری===
*[[آپاردی]]
===ریشه لغت===
*{{زبان بهاری}}
===آوایش===
*/آپاردِ/
===[[file:Nuvola apps bookcase2.png|40px]] صفت ===
'''آپاردِ'''
#(گفتگو): چیزی یا کسی را با اجازه یا بدون اجازه گرفتن و بردن، مانند اتومبیل خویشان را امانت گرفته و ببرند.
#(کنایه): ربودن، دزدیدن؛ مانند کلاغی که سر بزنگاه جوجهای برباید.
#:''ماَمورلَر توت [[دِ]] آپار [[دِ]]''.
#::ماموران گرفتن بردن.
[[رده:گویش بهاری]]
d6ys06184ogz7ao4z0kb9sty4n5r8os
آثوربان
0
201050
909029
865669
2026-07-31T04:23:39Z
Kunakes
46856
آثوربان/اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909029
wikitext
text/x-wiki
[[File:People of Medes 04.jpg|بندانگشتی|220px|چپ|آخرین نفر آثوربان]]
==فارسی==
===ریشه لغت===
*{{فارسی}}
===آوایش===
*/آثور/بان/
===[[file:Nuvola apps bookcase2.png|32px]] اسم مرکب===
'''آثوربان'''
#{{قدیم}}: طبقه [[موبدان]]، یکی از طبقات چهارگانه اجتماعی در زمان [[جمشید]] بنا به روایت [[شاهنامه]]. [[آتورنیان]]، [[آثروان]].
#:آثوربان ممکن است به صورت [[آسوربان]] نیز انشاء گردد و در [[زبان معیار باستان]] ممکن است به معنی فرمانده سواره نظام یا رئیس [[کاروان]] بوده باشد.
====منابع====
*فرهنگ بزرگ سخن
*فرهنگ شمس
[[رده:اسمهای مرکب آذری]]
[[رده:اسمهای مرکب ترکی]]
[[رده:کهن واژه]]
[[رده:اصطلاحات شاهنامه]]
[[رده:اصطلاحات با پیشوند آ]]
[[رده:اصطلاحات با میانوند ثور]]
[[رده:اصطلاحات با پسوند بان]]
is3hkr81ucwfy4zofymq82z24swkv1b
آجلا
0
201151
909036
865847
2026-07-31T05:35:08Z
Kunakes
46856
آجلا/اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909036
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{عربی}}
===آوایش===
*/آجِلاً/
===[[file:Nuvola apps bookcase2.png|30px]] قید===
'''آجلاً'''
#{{قدیم}}: در زمان آینده.
#:آجلاً ممکن است در [[زبان معیار باستان]] به دو بخش [[آجل]] - [[اَن]] قابل تجزیه باشد، که مفهوم آن پیاده شدن یک یا گروهی از اعضای ایل گرسنگان یا همان [[ابنسبیل]] بوده است.
====منابع====
*فرهنگ بزرگ سخن
[[رده:قیدهای عربی]]
[[رده:قید]]
[[رده:کهن واژه]]
[[رده:زبان معیار باستان]]
[[رده:اصطلاحات با پیشوند آج]]
[[رده:اصطلاحات با پسوند ان]]
6dey3cg7x2kry72lvifctqttj4njyqb
آجیدهدوز
0
201163
909039
865874
2026-07-31T05:59:07Z
Kunakes
46856
آجیدهدوز/اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909039
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{ترکی}}
===آوایش===
*/آجیدِه/دوز/
===[[file:Nuvola apps bookcase2.png|30px]] صفت===
'''آجیدهدوز'''
#آنکه [[گیوه]] آجیده میدوزد.
#:در آنجا شغلهایی دیدم که هرگز اسم آن به گوشم نرسیده بود، از قبیل ... آجیدهدوز. «[[جمالزاده]]»
===مترادفها===
*[[آجیده]]، [[آجیدهدوزی]]
====منابع====
*فرهنگ بزرگ سخن
[[رده:صفت]]
[[رده:صفتها]]
[[رده:اسمهای مرکب ترکی]]
[[رده:واژههای مرکب آذری]]
rwlxbquxvu99fnx8v7slbupxlavgaph
آجغورت
0
201207
909034
865954
2026-07-31T05:23:51Z
Kunakes
46856
آجغورت/اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909034
wikitext
text/x-wiki
==فارسی==
===گونههای دیگر نوشتاری===
*[[آژغورت]]
===ریشه لغت===
*{{زبان بهاری}}
===آوایش===
*/آج/غورت/
===[[file:Nuvola apps bookcase2.png|30px]] صفت ===
'''آجغورت'''
#(کنایه): هم معنی گرگ گرسنه، زمانی صرف میشود که فرد یا افرادی بسیار درمانده، مترصد فرصتی برای اخاذی به اشکال عوامفریبانه از آشنا یا غریبه باشند.
#:''آجغورت کِمِن دارتْ [[دِ]]''.
#::مانند گرگ گرسنه غارت نمود.
[[رده:گویش بهاری]]
sftb8bxwn2uklo6vx9cwlzork89x0f9
آتلان
0
207999
909027
881215
2026-07-31T04:13:15Z
Kunakes
46856
آتلان/اصلاح ردهها و حذف منبع مندرآوردی «فرهنک شمس»
909027
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{ترکی}}
===آوایش===
*/آتلان/
===[[file:Nuvola apps bookcase2.png|32px]] اسم===
'''آتلان'''
#به معنی پریدن یا سوار شدن بر اسب تفهیم میشده؛ اما در واقع نوعی تجهیز شدن به لباس رزم و اسب بوده است. [[آتدان]]، [[آتلانآتلان]].
===[[تصویر:Noia 64 apps kdict.png|px32]] کهن واژه===
#آتلان ممکن است در [[زبان معیار باستان]] به دو بخش یا کلمه [[آت]] - [[لان]] قابل تجزیه باشد؛ که معنی دستوری مسلح شدن به [[اسب]] بوده تا ماموریتی انجام شود.
[[رده:اسمهای مرکب ترکی]]
[[رده:واژههای مرکب آذری]]
[[رده:گویش بهاری]]
[[رده:زبان معیار باستان]]
[[رده:حوزه نفوذ فرهنگ آذرگشسپ]]
[[رده:کهن واژه]]
[[رده:اصطلاحات با پیشوند آت]]
[[رده:اصطلاحات با پسوند لان]]
s6fo1p6jqw0mtk04nffwlpshr72v83l
آجخدن
0
210303
909033
887201
2026-07-31T05:19:50Z
Kunakes
46856
آجخدن/اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909033
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{زبان بهاری}}
===آوایش===
*/آجِخْدَن/
===[[تصویر:Nuvola apps bookcase2.png|30px]] اسم===
'''آجخدن'''
#مترادف [[آهیختن]]، یا از [[حسادت]]، یا از [[گرسنگی]] در زبان فارسی.
#:''آجخدن دِیره''!
#::از حسودی میگه!
[[رده:گویش بهاری]]
mfisq7pkoyzz7rngovnmcbquyve1oux
آتدانیس
0
211369
909022
899257
2026-07-31T03:46:26Z
Kunakes
46856
آتدانیس/اصلاح رده و حذف منبع «فرهنگ شمس»
909022
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{دهش|یونانی باستان|آتلانتیس}}
===آوایش===
*/آتْدان/نیس/
===[[file:Nuvola apps bookcase2.svg|35px]] اسم مرکب===
'''آتدانیس'''
#مترادف [[پریدید]] در زبان فارسی، زمانی صرف میگردد که پرسش یا سؤالی از یکی راجع به پریدن یا نپریدن باشد. [[آتدانیک]].
#:''سودَن آتداننیس''؟
#::از آب پریدید؟
[[رده:گویش بهاری]]
[[رده:اصطلاحات با پیشوند آت]]
[[رده:اصطلاحات میانوند دان]]
[[رده:اصطلاحات با پسوند ایس]]
t4zqx7v348dt4n6kk5rkkjofcuetmg9
آتِل
0
213891
909025
901604
2026-07-31T04:04:00Z
Kunakes
46856
آتِل/اصلاح ردهها و حذف منبع مندرآوردی «فرهنگ شمس»
909025
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{دهش|فرانسوی|آتل}}
===آوایش===
*/آتِل/
===[[file:Nuvola apps bookcase2.png|40px]] اسم===
'''آتل'''
#مترادف مضارع [[پریدن]] در فارسی، [[بپر]].
#مترادف «ایل اسبداران» یا «ایل سوارکاران» در فارسی.
===[[تصویر:Noia 64 apps kdict.png|32px]] کهنواژه===
#آتل ممکن است در [[زبان معیار باستان]] بصورت [[آت]] - [[ائل]] قابل تجزیه باشد و معنی آن همان دو مورد [[گویش بهاری]] بوده که در ساختار جمله تفکیک میشوند.
[[رده:گویش بهاری]]
[[رده:کهن واژه]]
[[رده:زبان معیار باستان]]
[[رده:حوزه نفوذ فرهنگ آذرگشسپ]]
[[رده:اصطلاحات پیشا هند و ایرانی]]
[[رده:اصطلاحات با پیشوند آتل]]
[[رده:اصطلاحات با پسوند آتل]]
atjzavqc4u8zxjxz5zcbvd958x6gm6e
آچماق
0
213892
909041
896430
2026-07-31T06:11:22Z
Kunakes
46856
آچماق/اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909041
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{زبان بهاری}}
===آوایش===
*/آچ/ماق/
===[[file:Nuvola apps bookcase2.png|30px]] فعل===
'''آچماق'''
#باز کردن، گشودن به ویژه گرههای سخت و کور الیاف و نخهای درهم تنیده. [[آچماخ]].
{{-}}
{{ترجمه-بالا}}
*{{انگلیسی}}: [[opening]]
{{ترجمه-پایین}}
[[رده:گویش بهاری]]
[[رده:اصطلاحات با پیشوند آچ]]
[[رده:اصطلاحات با پسوند ماق]]
[[En:opening]]
b4wychavloqncw4mh3pvab7gw74ew9a
آتدانیک
0
215075
909024
899256
2026-07-31T04:00:22Z
Kunakes
46856
آتدانیک/اصلاح ردهها و حذف منبع مندرآوری «فرهنگ شمس»
909024
wikitext
text/x-wiki
==فارسی==
===ریشه لغت===
*{{دهش|یونانی باستان|آتلانتیک}}
===آوایش===
*/آتْ/دان/نیک/
===[[file:Nuvola apps bookcase2.svg|35px]] اسم مرکب===
'''آتدانیک'''
#مترادف [[پریدیم]] در فارسی، دسته جمعی پریدن در مواقی خاص مانند پریدن از کِشتیِ گرفتار طوفان. [[آتدانیس]].
#:''آتدان نیک سو ایچه نَع!''
#::پریدیم توی آب!
[[رده:گویش بهاری]]
[[رده:اصطلاحات با پیشوند آت]]
[[رده:اصطلاحات با میانوند دان]]
[[رده:اصطلاحات با پسوند نیک]]
9miubgvh15pvhnmyrjj04gvj5l9are1
آلاغ
0
216263
909051
901445
2026-07-31T11:50:06Z
Kunakes
46856
Kunakes صفحهٔ [[آلاق]] را به [[آلاغ]] منتقل کرد
901445
wikitext
text/x-wiki
==فارسی==
[[پرونده:Herbage Bahar2.jpg|بندانگشتی|245px|چپ|علفهرز]]
===ریشه لغت===
*{{زبان بهاری}}
===آوایش===
*/آلاق/
===[[file:Nuvola apps bookcase2.png|35px]] اسم بهاری===
'''آلاق'''
#مترادف [[علف هرز]] در فارسی، گیاهانی که برای زراعت مضر هستند. [[آلاغ]].
====منابع====
*فرهنگ شمس
[[رده:اسمهای زبان بهاری]]
[[رده:گیاهان]]
[[رده:فرهنگ شمس]]
ojtwolczinq7i3gok7xy23rzdqa2usu
909053
909051
2026-07-31T11:54:34Z
Kunakes
46856
آلاغ/ویرایش، اصلاح ردهها و حذف منبع فاقد شابک «فرهنگ شمس»
909053
wikitext
text/x-wiki
==فارسی==
[[پرونده:Herbage Bahar2.jpg|بندانگشتی|245px|چپ|گونههای مختلف [[علفهرز]]]]
===ریشه شناسی===
*{{زبان بهاری}}
===آوایش===
*/آلاغ/
===[[file:Nuvola apps bookcase2.png|35px]] اسم بهاری===
'''آلاغ'''
#مترادف [[علف هرز]] در فارسی، گیاهانی که برای زراعت مضر هستند.
[[رده:گویش بهاری]]
[[رده:گیاهان]]
5zu0keu904nocx7m8r4iznopmam3q11
بحث کاربر:Kunakes
3
219456
909018
908924
2026-07-31T03:23:27Z
Kunakes
46856
بحث کاربر:kunakes/پیام اخطار
909018
wikitext
text/x-wiki
== خوش آمدید ==
[[ویکیواژه:تازهواردان، خوش آمدید|'''به ویکیواژه خوشآمدید''']]!<br />
امیدوارم حضوری طولانی مدت در ویکیواژه داشته باشید و بتوانیم با همکاری یکدیگر در جهت پیشرفت هر چه بیشتر این وبگاه گام برداریم. ویکیواژه منتظر نوشتارهای خوب شماست.<br />
این پیوندها میتواند سودمند باشد:
{|
|-
|[[پرونده:Noia 64 apps help index.png|30px|right|link=ویکیواژه:راهنما|راهنما]] || [[ویکیواژه:راهنما|راهنما]] برای راهنمایی گرفتن
|-
| [[پرونده:Nuvola apps ksig.png|30px|right|link=ویکیواژه:آموزش سریع|آموزش سریع]] || [[ویکیواژه:آموزش سریع|آموزش سریع]]
|-
|||[[راهنما:افزودن سرواژه نو|افزودن سرواژهٔ نو]] راهنمای افزودن سرواژهٔ نو
|-
|||[[ویکیواژه:شیوهنامه|شیوهنامه]] شیوهنامهٔ ویکیواژه
|-
|[[File:Bucket in the sand.svg|right|50px]]||[[ویکیواژه:صفحه تمرین|صفحه تمرین]] برای تمرین ویرایش
|-
| [[پرونده:Nuvola apps chat.png|30px|right|link=ویکیواژه:قهوهخانه|قهوهخانه]]||[[ویکیواژه:قهوهخانه|قهوهخانه]] برای گفتگو دربارهٔ مسائل فنی و سیاستها.
|-
|[[پرونده:Farsi.svg|30px|right|link=ویکیواژه:فارسینویسی|فارسینویسی]]||[[ویکیواژه:فارسینویسی|فارسینویسی]]
|-
|[[پرونده:Nuvola apps agent.svg|30px|right|link=w:ویکیپدیا:خودآموز|آموزش پیشرفته]]||[[w:ویکیپدیا:خودآموز|آموزش پیشرفته]]
|-
|
|-
|
|-
|'''پروژههای دیگر بنیاد'''||[[پرونده:Wikipedia-logo.png|25px|link=w:صفحه_اصلی|ویکیپدیا]][[پرونده:HSWikimedia.svg|30px|link=m:Special:Recentchanges|فراویکی]] [[پرونده:HSCommons.svg|30px|link=commons:Special:Recentchanges|ویکیانبار]][[پرونده:HSWNews.svg|30px|link=n:ویژه:تغییرات اخیر|ویکیخبر]] [[پرونده:HSWBooks.svg|30px|link=b:صفحه_اصلی|ویکینَسک]] [[پرونده:HSWVoyage.svg|30px|link=voy:صفحه_اصلی|ویکیسفر]][[پرونده:HSWQuote.svg|30px|link=q:صفحه_اصلی|ویکیگفتاورد]][[پرونده:HSWSource.svg|30px|link=s:صفحه_اصلی|ویکینبشته]]
|}
امیدوارم از ''[[ویکیواژه:ویکیواژه نویسان|ویکیواژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~~~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکیواژه:قهوهخانه|قهوهخانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد میگویم! شاد باشید!
-- [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۷ ژوئیهٔ ۲۰۲۶، ساعت ۱۴:۴۳ (UTC) ۲۷ ژوئیهٔ ۲۰۲۶، ساعت ۰۶:۰۸ (UTC)
== استفاده از چند حساب کاربری ==
درود. توجه داشته باشید که اگر چه داشتن چند حساب کاربری در ویکیواژه مجاز است، امّا اگر ارتباط بین این حسابها را علنی نکرده باشید اجازه ندارید از آنها به شکل همجهت استفاده کنید؛ حسابهای جایگزینی که مالک اصلی آنها اعلام نشده باشد نباید در مباحث داخلی پروژه استفاده شوند. برای اطلاعات بیشتر [[وو:حساب زاپاس|حساب زاپاس]] را بخوانید. توجه کنید که [[وپ:زاغ|استفادهٔ غیر مجاز]] از چند حساب کاربری میتواند به قطع دسترسی تمام این حسابها منجر بشود. این امر شامل مشارکت از طریق ترکیبی از حسابهای ثبتنام کرده و گمنام (آیپی) نیز میشود.
اگر چند حساب دارید میتوانید رابطهٔ آنها را مثلاً با کمک الگوی {{الگوی|حساب جایگزین}} علنی کنید.
[[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۲۷ ژوئیهٔ ۲۰۲۶، ساعت ۱۸:۲۲ (UTC)
::ضمن عرض خسته نباشید جناب مدیر [[کاربر:The.shahab|The.shahab]]، فعلا با همین حساب کاربری به جمع شما اساتید پیوستم و از حساب جایگزین نیز اطلاعی نداشتم، با سپاس مجدد. [[کاربر:Kunakes|Kunakes]] ([[بحث کاربر:Kunakes|بحث]]) ۲۸ ژوئیهٔ ۲۰۲۶، ساعت ۰۵:۳۳ (UTC)
== کوناکس؟ ==
سلام. نام کاربری عجیب شما را پیشتر کاربران خرابکار بصورت سرواژه درست کردهاند. دلیل ایجاد حساب کاربری با این نام را بفرمایید. اگر شما همان کاربر خرابکار هستید، برای اینکه وقت یکدیگر را نگیریم، اعلام کنید. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۲۹ ژوئیهٔ ۲۰۲۶، ساعت ۱۲:۵۳ (UTC)
::سلام جناب مدیر [[The.shahab|The.shahab]]، عنوان مزبور نام جنگ یا محل نبردی بود میان خاندان هخامنشی در میانرودان، ظاهرا میان اردشیر دوم هخامنشی و برادرش کوروش دوم یا سوم هخامنشی. این نام را دست داشتم هیچ موردی ندارد. [[کاربر:Kunakes|Kunakes]] ([[بحث کاربر:Kunakes|بحث]]) ۲۹ ژوئیهٔ ۲۰۲۶، ساعت ۱۲:۵۹ (UTC)
:::[[ویکیواژه:شروع پاک|اینجا]] را مطالعه بفرمایید. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۲۹ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۰۷ (UTC)
::::نه جناب مدیر، بنده هیچ سابقه حساب کاربری و ویرایش در ویکیهای فارسی بویژه ویکیواژه فارسی را نداشتم اگر هم به مسیر اصلاحات برخی سرواژههای امروز راه یافتنم نکاتی بود که این کاربر [[کاربر:Khosrozadeh]] نام برده بود؛ اگر اشکال دارد فقط از سرواژههای شروع خواهم کرد. [[کاربر:Kunakes|Kunakes]] ([[بحث کاربر:Kunakes|بحث]]) ۲۹ ژوئیهٔ ۲۰۲۶، ساعت ۱۳:۳۱ (UTC)
:::::حال که مشغول اصلاح نام ردهها و منابع هستید، لطفاً هر جا رده:گویش بهاری یا رده:زبان بهاری یا در متن سرواژه واژه زبان یا گویش بهاری دیدید، همه را پاک کنید. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۲۹ ژوئیهٔ ۲۰۲۶، ساعت ۱۴:۰۲ (UTC)
::::::اقدام خواهد شد، ولی هم نیاز به زمان حداقل یک سال دارد و هم اختیار یا ابزار. جناب مدیر آیا پاک کردن همان حذف کردن است یا اصلاح کردن، اگر حذف باشد شاید به ابزار و اختیار نیاز داشته باشم. در این مورد راهنمایی بفرمایید.[[کاربر:Kunakes|Kunakes]] ([[بحث کاربر:Kunakes|بحث]]) ۲۹ ژوئیهٔ ۲۰۲۶، ساعت ۱۶:۳۸ (UTC)
:::::::با پاککردن، فقط محتوا حذف میشود ولی خود سرواژه یا رده باید توسط مدیر حذف شود. اگر با چنین موردی برخورد داشتید، {{}} را بنویسید و وسطش بنویسید «حذف سریع» یا بصورت کوتاه «حس». برای اطلاعات بیشتر، [[الگو:حذف سریع|اینجا]] را مطالعه بفرمایید. [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۲۹ ژوئیهٔ ۲۰۲۶، ساعت ۱۹:۴۵ (UTC)
==پیام اخطار==
مدیر محترم [[کاربر:The.shahab|The.shahab]]، امروز با آغاز فعالیت پاکسازی و الصاق برچسب حذف سریع روی سرواژههای بیربط با زبان فارسی، با پیام هشدار یا اخطار خرابکاری مواجه شدم. بنابر این از رویه دیروز انصراف نموده ریشه و قیچی را به شما مدیران عزیز سپرده و به کار عادی یعنی اصلاح سرواژههای و ایجاد سرواژههای جدید خواهم پرداخت. باتشکر [[کاربر:Kunakes|Kunakes]] ([[بحث کاربر:Kunakes|بحث]]) ۳۱ ژوئیهٔ ۲۰۲۶، ساعت ۰۳:۲۲ (UTC)
qdckf04ppwb0is194x6dmflttge5ghy
بحث کاربر:Saufllah
3
219473
908977
2026-07-30T15:15:45Z
پیام به کاربر جدید
26498
افزودن [[الگو:خوشامدید|پیام خوشآمد]] به صفحهٔ بحث کاربر تازه
908977
wikitext
text/x-wiki
== خوش آمدید ==
[[ویکیواژه:تازهواردان، خوش آمدید|'''به ویکیواژه خوشآمدید''']]!<br />
امیدوارم حضوری طولانی مدت در ویکیواژه داشته باشید و بتوانیم با همکاری یکدیگر در جهت پیشرفت هر چه بیشتر این وبگاه گام برداریم. ویکیواژه منتظر نوشتارهای خوب شماست.<br />
این پیوندها میتواند سودمند باشد:
{|
|-
|[[پرونده:Noia 64 apps help index.png|30px|right|link=ویکیواژه:راهنما|راهنما]] || [[ویکیواژه:راهنما|راهنما]] برای راهنمایی گرفتن
|-
| [[پرونده:Nuvola apps ksig.png|30px|right|link=ویکیواژه:آموزش سریع|آموزش سریع]] || [[ویکیواژه:آموزش سریع|آموزش سریع]]
|-
|||[[راهنما:افزودن سرواژه نو|افزودن سرواژهٔ نو]] راهنمای افزودن سرواژهٔ نو
|-
|||[[ویکیواژه:شیوهنامه|شیوهنامه]] شیوهنامهٔ ویکیواژه
|-
|[[File:Bucket in the sand.svg|right|50px]]||[[ویکیواژه:صفحه تمرین|صفحه تمرین]] برای تمرین ویرایش
|-
| [[پرونده:Nuvola apps chat.png|30px|right|link=ویکیواژه:قهوهخانه|قهوهخانه]]||[[ویکیواژه:قهوهخانه|قهوهخانه]] برای گفتگو دربارهٔ مسائل فنی و سیاستها.
|-
|[[پرونده:Farsi.svg|30px|right|link=ویکیواژه:فارسینویسی|فارسینویسی]]||[[ویکیواژه:فارسینویسی|فارسینویسی]]
|-
|[[پرونده:Nuvola apps agent.svg|30px|right|link=w:ویکیپدیا:خودآموز|آموزش پیشرفته]]||[[w:ویکیپدیا:خودآموز|آموزش پیشرفته]]
|-
|
|-
|
|-
|'''پروژههای دیگر بنیاد'''||[[پرونده:Wikipedia-logo.png|25px|link=w:صفحه_اصلی|ویکیپدیا]][[پرونده:HSWikimedia.svg|30px|link=m:Special:Recentchanges|فراویکی]] [[پرونده:HSCommons.svg|30px|link=commons:Special:Recentchanges|ویکیانبار]][[پرونده:HSWNews.svg|30px|link=n:ویژه:تغییرات اخیر|ویکیخبر]] [[پرونده:HSWBooks.svg|30px|link=b:صفحه_اصلی|ویکینَسک]] [[پرونده:HSWVoyage.svg|30px|link=voy:صفحه_اصلی|ویکیسفر]][[پرونده:HSWQuote.svg|30px|link=q:صفحه_اصلی|ویکیگفتاورد]][[پرونده:HSWSource.svg|30px|link=s:صفحه_اصلی|ویکینبشته]]
|}
امیدوارم از ''[[ویکیواژه:ویکیواژه نویسان|ویکیواژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~~~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکیواژه:قهوهخانه|قهوهخانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد میگویم! شاد باشید!
-- [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۷ ژوئیهٔ ۲۰۲۶، ساعت ۱۴:۴۳ (UTC) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۱۵:۱۵ (UTC)
ifip6rp0l8yc1hmj92cyk0ygl1h9mi7
الگو:دست نگهدار
10
219474
908978
2026-07-30T15:23:59Z
Mamouri
34239
ایجاد الگوی دست نگهدار برای مخالفت با حذف سریع
908978
wikitext
text/x-wiki
<includeonly>{{mbox
| type = notice
| image = [[پرونده:Stop hand nuvola.svg|40px]]
| text = '''دست نگه دارید!''' پدیدآورندهٔ این صفحه یا کاربری دیگر با حذف سریع آن مخالف است و دلیل خود را در زیر آوردهاست. لطفاً پیش از حذف، آن دلیل را بررسی کنید. افزودن این الگو مانع حذف صفحه در صورت صدق [[وو:محس|معیارهای حذف سریع]] نمیشود، ولی به بررسی دقیقتر کمک میکند.
}}</includeonly><noinclude>
{{توضیحات}}
</noinclude>
93h4rny5gfxy97ds7epd54qtgdk2gij
ویکیواژه:محس
4
219475
908979
2026-07-30T15:24:44Z
Mamouri
34239
ایجاد صفحهٔ معیارهای حذف سریع
908979
wikitext
text/x-wiki
{{میانبر|وو:محس}}
'''معیارهای حذف سریع''' فهرست کوتاهی از وضعیتهایی است که در آنها مدیران میتوانند بدون بحث و درنگ صفحهای را حذف کنند. این معیارها تنها روشنیترین موارد را دربرمیگیرند؛ اگر صفحهای در هیچیک از آنها نگنجد، برای حذف آن باید از روند عادی حذف بهره برد و نباید نامزد حذف سریع شود.
برای نامزد کردن یک صفحه، الگوی <code><nowiki>{{حذف سریع|دلیل}}</nowiki></code> را در بالای آن بگذارید و بهجای «دلیل» یکی از دلیلهای زیر یا توضیح کوتاه خود را بنویسید. اگر با حذف مخالفید، الگوی <code><nowiki>{{دست نگهدار}}</nowiki></code> را زیر برچسب بیفزایید و دلیلتان را بنویسید.
== معیارها ==
؛ حذف شده : صفحهای که پیشتر پس از بحث یا بر پایهٔ همین معیارها حذف شده و بی هیچ تغییر معناداری دوباره ساخته شدهاست.
؛ نقض حق تکثیر : صفحهای که آشکارا رونوشت از یک منبع دارای حق تکثیر است و هیچ محتوای آزاد قابلنگهداری ندارد.
؛ تکراری : مدخل یا پروندهای که نسخهٔ یکسان آن هماکنون در ویکیواژه یا [[ویکیانبار]] موجود است.
؛ استفاده نشده : الگو، رده یا پروندهای که در هیچ صفحهای به کار نرفته و کاربردی برای آن متصور نیست.
؛ بحث یتیم : صفحهٔ بحثی که صفحهٔ اصلی متناظر آن وجود ندارد یا حذف شدهاست.
؛ منتقلشده : محتوایی که به پروژهٔ دیگری از بنیاد ویکیمدیا منتقل شده و ماندن آن در ویکیواژه لازم نیست.
؛ خرابکاری : صفحهای که تنها برای خرابکاری، آزمایش یا محتوای بیمعنا ساخته شدهاست.
صفحههای نامزدشده در [[:رده:صفحههای نامزد حذف سریع]] گرد میآیند.
2yv6sznhmljg90xatv6h3yxggntmhkp
908980
908979
2026-07-30T15:25:30Z
Mamouri
34239
اصلاح پیوند ویکیانبار به میانویکی
908980
wikitext
text/x-wiki
{{میانبر|وو:محس}}
'''معیارهای حذف سریع''' فهرست کوتاهی از وضعیتهایی است که در آنها مدیران میتوانند بدون بحث و درنگ صفحهای را حذف کنند. این معیارها تنها روشنیترین موارد را دربرمیگیرند؛ اگر صفحهای در هیچیک از آنها نگنجد، برای حذف آن باید از روند عادی حذف بهره برد و نباید نامزد حذف سریع شود.
برای نامزد کردن یک صفحه، الگوی <code><nowiki>{{حذف سریع|دلیل}}</nowiki></code> را در بالای آن بگذارید و بهجای «دلیل» یکی از دلیلهای زیر یا توضیح کوتاه خود را بنویسید. اگر با حذف مخالفید، الگوی <code><nowiki>{{دست نگهدار}}</nowiki></code> را زیر برچسب بیفزایید و دلیلتان را بنویسید.
== معیارها ==
؛ حذف شده : صفحهای که پیشتر پس از بحث یا بر پایهٔ همین معیارها حذف شده و بی هیچ تغییر معناداری دوباره ساخته شدهاست.
؛ نقض حق تکثیر : صفحهای که آشکارا رونوشت از یک منبع دارای حق تکثیر است و هیچ محتوای آزاد قابلنگهداری ندارد.
؛ تکراری : مدخل یا پروندهای که نسخهٔ یکسان آن هماکنون در ویکیواژه یا [[commons:|ویکیانبار]] موجود است.
؛ استفاده نشده : الگو، رده یا پروندهای که در هیچ صفحهای به کار نرفته و کاربردی برای آن متصور نیست.
؛ بحث یتیم : صفحهٔ بحثی که صفحهٔ اصلی متناظر آن وجود ندارد یا حذف شدهاست.
؛ منتقلشده : محتوایی که به پروژهٔ دیگری از بنیاد ویکیمدیا منتقل شده و ماندن آن در ویکیواژه لازم نیست.
؛ خرابکاری : صفحهای که تنها برای خرابکاری، آزمایش یا محتوای بیمعنا ساخته شدهاست.
صفحههای نامزدشده در [[:رده:صفحههای نامزد حذف سریع]] گرد میآیند.
acagufuir9czx989yigexzm3fj27g3c
ویکیواژه:معیارهای حذف سریع
4
219476
908981
2026-07-30T15:41:15Z
Mamouri
34239
تغییرمسیر به معیارهای حذف سریع
908981
wikitext
text/x-wiki
#تغییرمسیر [[ویکیواژه:محس]]
pseifq3u1s5lbec8likkampw4yfj2sb
رده:پیشنهادهای حذف
14
219477
908983
2026-07-30T15:51:06Z
Mamouri
34239
ایجاد ردهٔ پنهان پیشنهادهای حذف
908983
wikitext
text/x-wiki
__HIDDENCAT__
صفحههایی که با الگوی [[الگو:پیشنهاد حذف]] برای حذف پیشنهاد شدهاند و در انتظار بررسیاند.
2d4f287lahignrgb8ocarlo9pb74wxp
الگو:پیشنهاد حذف
10
219478
908984
2026-07-30T15:51:56Z
Mamouri
34239
ایجاد الگوی پیشنهاد حذف
908984
wikitext
text/x-wiki
<includeonly>{{mbox
| type = delete
| image = [[پرونده:Ambox warning yellow.svg|40px]]
| text = این صفحه بهعنوان '''[[ویکیواژه:پیشنهاد حذف|پیشنهاد حذف]]''' نشاندار شدهاست، با این دلیل:<br>«''{{{1|{{{دلیل|بدون دلیل}}}}}}''»<br>
اگر با حذف این صفحه مخالفید، آزادید این الگو را بردارید؛ ولی لطفاً بهجای برداشتن دوبارهٔ آن در صورت اختلاف، موضوع را در صفحهٔ بحث مطرح کنید. اگر ظرف یک هفته کسی مخالفتی نکند، این صفحه ممکن است بدون بحث بیشتر حذف شود.
}}[[رده:پیشنهادهای حذف]]</includeonly><noinclude>
{{توضیحات}}
</noinclude>
iluuqtpc7w7g0rh7epybjj3tv753u3r
ویکیواژه:پیشنهاد حذف
4
219479
908985
2026-07-30T15:53:38Z
Mamouri
34239
ایجاد صفحهٔ روند پیشنهاد حذف
908985
wikitext
text/x-wiki
{{میانبر|وو:پح}}
'''پیشنهاد حذف''' روشی برای حذف صفحههایی است که بهروشنی در [[وو:محس|معیارهای حذف سریع]] نمیگنجند، ولی گمان میرود بتوان بیبحث چندانی آنها را حذف کرد.
برای پیشنهاد حذف یک صفحه، الگوی <code><nowiki>{{پیشنهاد حذف|دلیل}}</nowiki></code> را در بالای آن بگذارید و دلیل خود را بنویسید. اگر ظرف یک هفته کسی مخالفتی نکند، هر مدیری میتواند صفحه را حذف کند.
هر کاربری میتواند با برداشتن الگو با حذف مخالفت کند. در این صورت صفحه نباید دوباره با همین روش پیشنهاد حذف شود و برای حذف آن باید از روند عادی بهره برد.
صفحههای پیشنهادشده در [[:رده:پیشنهادهای حذف]] گرد میآیند.
''این روش تازه پیشنهاد شدهاست و بازخورد و اصلاح آن بهدست جامعهٔ ویکیواژه خوشایند است.''
i6giycysnxj9pwicaszyrf2fsmqps4j
ویکیواژه:پح
4
219480
908986
2026-07-30T15:54:34Z
Mamouri
34239
میانبر
908986
wikitext
text/x-wiki
#تغییرمسیر [[ویکیواژه:پیشنهاد حذف]]
lzjto9vamnep6hhd88phgybdv0rkfdb
کاربر:Mamouri/twinkle-bundle.js
2
219481
908987
2026-07-30T15:58:13Z
Mamouri
34239
بهروزرسانی بستهٔ آزمایشی توینکل
908987
javascript
text/javascript
// <nowiki>
// توینکل برای ویکیواژهٔ فارسی — بستهٔ آزمایشی (کاربر:Mamouri)
/* ===== morebits.js ===== */
// <nowiki>
/**
* morebits.js
* ===========
* A library full of lots of goodness for user scripts on MediaWiki wikis, including Wikipedia.
*
* The highlights include:
* - Morebits.quickForm class - generates quick HTML forms on the fly
* - Morebits.wiki.api class - makes calls to the MediaWiki API
* - Morebits.wiki.page class - modifies pages on the wiki (edit, revert, delete, etc.)
* - Morebits.wikitext class - contains some utilities for dealing with wikitext
* - Morebits.status class - a rough-and-ready status message displayer, used by the Morebits.wiki classes
* - Morebits.simpleWindow class - a wrapper for jQuery UI Dialog with a custom look and extra features
*
* Dependencies:
* - The whole thing relies on jQuery. But most wikis should provide this by default.
* - Morebits.quickForm, Morebits.simpleWindow, and Morebits.status rely on the "morebits.css" file for their styling.
* - Morebits.simpleWindow relies on jquery UI Dialog (from ResourceLoader module name 'jquery.ui').
* - Morebits.quickForm tooltips rely on Tipsy (ResourceLoader module name 'jquery.tipsy').
* For external installations, Tipsy is available at [http://onehackoranother.com/projects/jquery/tipsy].
* - To create a gadget based on morebits.js, use this syntax in MediaWiki:Gadgets-definition:
* * GadgetName[ResourceLoader|dependencies=mediawiki.user,mediawiki.util,jquery.ui,jquery.tipsy]|morebits.js|morebits.css|GadgetName.js
* - Alternatively, you can configure morebits.js as a hidden gadget in MediaWiki:Gadgets-definition:
* * morebits[ResourceLoader|dependencies=mediawiki.user,mediawiki.util,jquery.ui,jquery.tipsy|hidden]|morebits.js|morebits.css
* and then load ext.gadget.morebits as one of the dependencies for the new gadget
*
* All the stuff here works on all browsers for which MediaWiki provides JavaScript support.
*
* This library is maintained by the maintainers of Twinkle.
* For queries, suggestions, help, etc., head to [[Wikipedia talk:Twinkle]] on English Wikipedia [http://en.wikipedia.org].
* The latest development source is available at [https://github.com/azatoth/twinkle/blob/master/morebits.js].
*/
(function (window, document, $) { // Wrap entire file with anonymous function
var Morebits = {};
window.Morebits = Morebits; // allow global access
/**
* **************** Morebits.userIsInGroup() ****************
* Simple helper function to see what groups a user might belong
* @param {string} group eg. `sysop`, `extendedconfirmed`, etc
* @returns {boolean}
*/
Morebits.userIsInGroup = function (group) {
return mw.config.get('wgUserGroups').indexOf(group) !== -1;
};
// Used a lot
Morebits.userIsSysop = Morebits.userIsInGroup('sysop');
/**
* **************** Morebits.sanitizeIPv6() ****************
* JavaScript translation of the MediaWiki core function IP::sanitizeIP() in
* includes/utils/IP.php.
* Converts an IPv6 address to the canonical form stored and used by MediaWiki.
* @param {string} address - The IPv6 address
* @returns {string}
*/
Morebits.sanitizeIPv6 = function (address) {
address = address.trim();
if (address === '') {
return null;
}
if (!mw.util.isIPv6Address(address)) {
return address; // nothing else to do for IPv4 addresses or invalid ones
}
// Remove any whitespaces, convert to upper case
address = address.toUpperCase();
// Expand zero abbreviations
var abbrevPos = address.indexOf('::');
if (abbrevPos > -1) {
// We know this is valid IPv6. Find the last index of the
// address before any CIDR number (e.g. "a:b:c::/24").
var CIDRStart = address.indexOf('/');
var addressEnd = CIDRStart > -1 ? CIDRStart - 1 : address.length - 1;
// If the '::' is at the beginning...
var repeat, extra, pad;
if (abbrevPos === 0) {
repeat = '0:';
extra = address === '::' ? '0' : ''; // for the address '::'
pad = 9; // 7+2 (due to '::')
// If the '::' is at the end...
} else if (abbrevPos === (addressEnd - 1)) {
repeat = ':0';
extra = '';
pad = 9; // 7+2 (due to '::')
// If the '::' is in the middle...
} else {
repeat = ':0';
extra = ':';
pad = 8; // 6+2 (due to '::')
}
var replacement = repeat;
pad -= address.split(':').length - 1;
for (var i = 1; i < pad; i++) {
replacement += repeat;
}
replacement += extra;
address = address.replace('::', replacement);
}
// Remove leading zeros from each bloc as needed
address = address.replace(/(^|:)0+([0-9A-Fa-f]{1,4})/g, '$1$2');
return address;
};
/**
* **************** Morebits.quickForm ****************
* Morebits.quickForm is a class for creation of simple and standard forms without much
* specific coding.
*
* Index to Morebits.quickForm element types:
*
* select A combo box (aka drop-down).
* - Attributes: name, label, multiple, size, list, event, disabled
* option An element for a combo box.
* - Attributes: value, label, selected, disabled
* optgroup A group of "option"s.
* - Attributes: label, list
* field A fieldset (aka group box).
* - Attributes: name, label, disabled
* checkbox A checkbox. Must use "list" parameter.
* - Attributes: name, list, event
* - Attributes (within list): name, label, value, checked, disabled, event, subgroup
* radio A radio button. Must use "list" parameter.
* - Attributes: name, list, event
* - Attributes (within list): name, label, value, checked, disabled, event, subgroup
* input A text box.
* - Attributes: name, label, value, size, disabled, required, readonly, maxlength, event
* dyninput A set of text boxes with "Remove" buttons and an "Add" button.
* - Attributes: name, label, min, max, sublabel, value, size, maxlength, event
* hidden An invisible form field.
* - Attributes: name, value
* header A level 5 header.
* - Attributes: label
* div A generic placeholder element or label.
* - Attributes: name, label
* submit A submit button. Morebits.simpleWindow moves these to the footer of the dialog.
* - Attributes: name, label, disabled
* button A generic button.
* - Attributes: name, label, disabled, event
* textarea A big, multi-line text box.
* - Attributes: name, label, value, cols, rows, disabled, required, readonly
* fragment A DocumentFragment object.
* - No attributes, and no global attributes except adminonly
*
* Global attributes: id, className, style, tooltip, extra, adminonly
*/
/**
* @constructor
* @param {event} event - Function to execute when form is submitted
* @param {string} [eventType=submit] - Type of the event (default: submit)
*/
Morebits.quickForm = function QuickForm(event, eventType) {
this.root = new Morebits.quickForm.element({ type: 'form', event: event, eventType: eventType });
};
/**
* Renders the HTML output of the quickForm
* @returns {HTMLElement}
*/
Morebits.quickForm.prototype.render = function QuickFormRender() {
var ret = this.root.render();
ret.names = {};
return ret;
};
/**
* Append element to the form
* @param {(Object|Morebits.quickForm.element)} data - a quickform element, or the object with which
* a quickform element is constructed.
* @returns {Morebits.quickForm.element} - same as what is passed to the function
*/
Morebits.quickForm.prototype.append = function QuickFormAppend(data) {
return this.root.append(data);
};
/**
* @constructor
* @param {Object} data - Object representing the quickform element. See class documentation
* comment for available types and attributes for each.
*/
Morebits.quickForm.element = function QuickFormElement(data) {
this.data = data;
this.childs = [];
this.id = Morebits.quickForm.element.id++;
};
Morebits.quickForm.element.id = 0;
/**
* Appends an element to current element
* @param {Morebits.quickForm.element} data A quickForm element or the object required to
* create the quickForm element
* @returns {Morebits.quickForm.element} The same element passed in
*/
Morebits.quickForm.element.prototype.append = function QuickFormElementAppend(data) {
var child;
if (data instanceof Morebits.quickForm.element) {
child = data;
} else {
child = new Morebits.quickForm.element(data);
}
this.childs.push(child);
return child;
};
/**
* Renders the HTML output for the quickForm element
* This should be called without parameters: form.render()
* @returns {HTMLElement}
*/
Morebits.quickForm.element.prototype.render = function QuickFormElementRender(internal_subgroup_id) {
var currentNode = this.compute(this.data, internal_subgroup_id);
for (var i = 0; i < this.childs.length; ++i) {
// do not pass internal_subgroup_id to recursive calls
currentNode[1].appendChild(this.childs[i].render());
}
return currentNode[0];
};
Morebits.quickForm.element.prototype.compute = function QuickFormElementCompute(data, in_id) {
var node;
var childContainder = null;
var label;
var id = (in_id ? in_id + '_' : '') + 'node_' + this.id;
if (data.adminonly && !Morebits.userIsSysop) {
// hell hack alpha
data.type = 'hidden';
}
var i, current, subnode;
switch (data.type) {
case 'form':
node = document.createElement('form');
node.className = 'quickform';
node.setAttribute('action', 'javascript:void(0);');
if (data.event) {
node.addEventListener(data.eventType || 'submit', data.event, false);
}
break;
case 'fragment':
node = document.createDocumentFragment();
// fragments can't have any attributes, so just return it straight away
return [ node, node ];
case 'select':
node = document.createElement('div');
node.setAttribute('id', 'div_' + id);
if (data.label) {
label = node.appendChild(document.createElement('label'));
label.setAttribute('for', id);
label.appendChild(document.createTextNode(data.label));
}
var select = node.appendChild(document.createElement('select'));
if (data.event) {
select.addEventListener('change', data.event, false);
}
if (data.multiple) {
select.setAttribute('multiple', 'multiple');
}
if (data.size) {
select.setAttribute('size', data.size);
}
if (data.disabled) {
select.setAttribute('disabled', 'disabled');
}
select.setAttribute('name', data.name);
if (data.list) {
for (i = 0; i < data.list.length; ++i) {
current = data.list[i];
if (current.list) {
current.type = 'optgroup';
} else {
current.type = 'option';
}
subnode = this.compute(current);
select.appendChild(subnode[0]);
}
}
childContainder = select;
break;
case 'option':
node = document.createElement('option');
node.values = data.value;
node.setAttribute('value', data.value);
if (data.selected) {
node.setAttribute('selected', 'selected');
}
if (data.disabled) {
node.setAttribute('disabled', 'disabled');
}
node.setAttribute('label', data.label);
node.appendChild(document.createTextNode(data.label));
break;
case 'optgroup':
node = document.createElement('optgroup');
node.setAttribute('label', data.label);
if (data.list) {
for (i = 0; i < data.list.length; ++i) {
current = data.list[i];
current.type = 'option'; // must be options here
subnode = this.compute(current);
node.appendChild(subnode[0]);
}
}
break;
case 'field':
node = document.createElement('fieldset');
label = node.appendChild(document.createElement('legend'));
label.appendChild(document.createTextNode(data.label));
if (data.name) {
node.setAttribute('name', data.name);
}
if (data.disabled) {
node.setAttribute('disabled', 'disabled');
}
break;
case 'checkbox':
case 'radio':
node = document.createElement('div');
if (data.list) {
for (i = 0; i < data.list.length; ++i) {
var cur_id = id + '_' + i;
current = data.list[i];
var cur_div;
if (current.type === 'header') {
// inline hack
cur_div = node.appendChild(document.createElement('h6'));
cur_div.appendChild(document.createTextNode(current.label));
if (current.tooltip) {
Morebits.quickForm.element.generateTooltip(cur_div, current);
}
continue;
}
cur_div = node.appendChild(document.createElement('div'));
subnode = cur_div.appendChild(document.createElement('input'));
subnode.values = current.value;
subnode.setAttribute('value', current.value);
subnode.setAttribute('name', current.name || data.name);
subnode.setAttribute('type', data.type);
subnode.setAttribute('id', cur_id);
if (current.checked) {
subnode.setAttribute('checked', 'checked');
}
if (current.disabled) {
subnode.setAttribute('disabled', 'disabled');
}
label = cur_div.appendChild(document.createElement('label'));
label.appendChild(document.createTextNode(current.label));
label.setAttribute('for', cur_id);
if (current.tooltip) {
Morebits.quickForm.element.generateTooltip(label, current);
}
// styles go on the label, doesn't make sense to style a checkbox/radio
if (current.style) {
label.setAttribute('style', current.style);
}
var event;
if (current.subgroup) {
var tmpgroup = current.subgroup;
if (!Array.isArray(tmpgroup)) {
tmpgroup = [ tmpgroup ];
}
var subgroupRaw = new Morebits.quickForm.element({
type: 'div',
id: id + '_' + i + '_subgroup'
});
$.each(tmpgroup, function(idx, el) {
var newEl = $.extend({}, el);
if (!newEl.type) {
newEl.type = data.type;
}
newEl.name = (current.name || data.name) + '.' + newEl.name;
subgroupRaw.append(newEl);
});
var subgroup = subgroupRaw.render(cur_id);
subgroup.className = 'quickformSubgroup';
subnode.subgroup = subgroup;
subnode.shown = false;
event = function(e) {
if (e.target.checked) {
e.target.parentNode.appendChild(e.target.subgroup);
if (e.target.type === 'radio') {
var name = e.target.name;
if (e.target.form.names[name] !== undefined) {
e.target.form.names[name].parentNode.removeChild(e.target.form.names[name].subgroup);
}
e.target.form.names[name] = e.target;
}
} else {
e.target.parentNode.removeChild(e.target.subgroup);
}
};
subnode.addEventListener('change', event, true);
if (current.checked) {
subnode.parentNode.appendChild(subgroup);
}
} else if (data.type === 'radio') {
event = function(e) {
if (e.target.checked) {
var name = e.target.name;
if (e.target.form.names[name] !== undefined) {
e.target.form.names[name].parentNode.removeChild(e.target.form.names[name].subgroup);
}
delete e.target.form.names[name];
}
};
subnode.addEventListener('change', event, true);
}
// add users' event last, so it can interact with the subgroup
if (data.event) {
subnode.addEventListener('change', data.event, false);
} else if (current.event) {
subnode.addEventListener('change', current.event, true);
}
}
}
break;
case 'input':
node = document.createElement('div');
node.setAttribute('id', 'div_' + id);
if (data.label) {
label = node.appendChild(document.createElement('label'));
label.appendChild(document.createTextNode(data.label));
label.setAttribute('for', data.id || id);
}
subnode = node.appendChild(document.createElement('input'));
if (data.value) {
subnode.setAttribute('value', data.value);
}
subnode.setAttribute('name', data.name);
subnode.setAttribute('type', 'text');
if (data.size) {
subnode.setAttribute('size', data.size);
}
if (data.disabled) {
subnode.setAttribute('disabled', 'disabled');
}
if (data.required) {
subnode.setAttribute('required', 'required');
}
if (data.readonly) {
subnode.setAttribute('readonly', 'readonly');
}
if (data.maxlength) {
subnode.setAttribute('maxlength', data.maxlength);
}
if (data.event) {
subnode.addEventListener('keyup', data.event, false);
}
childContainder = subnode;
break;
case 'dyninput':
var min = data.min || 1;
var max = data.max || Infinity;
node = document.createElement('div');
label = node.appendChild(document.createElement('h5'));
label.appendChild(document.createTextNode(data.label));
var listNode = node.appendChild(document.createElement('div'));
var more = this.compute({
type: 'button',
label: 'more',
disabled: min >= max,
event: function(e) {
var new_node = new Morebits.quickForm.element(e.target.sublist);
e.target.area.appendChild(new_node.render());
if (++e.target.counter >= e.target.max) {
e.target.setAttribute('disabled', 'disabled');
}
e.stopPropagation();
}
});
node.appendChild(more[0]);
var moreButton = more[1];
var sublist = {
type: '_dyninput_element',
label: data.sublabel || data.label,
name: data.name,
value: data.value,
size: data.size,
remove: false,
maxlength: data.maxlength,
event: data.event
};
for (i = 0; i < min; ++i) {
var elem = new Morebits.quickForm.element(sublist);
listNode.appendChild(elem.render());
}
sublist.remove = true;
sublist.morebutton = moreButton;
sublist.listnode = listNode;
moreButton.sublist = sublist;
moreButton.area = listNode;
moreButton.max = max - min;
moreButton.counter = 0;
break;
case '_dyninput_element': // Private, similar to normal input
node = document.createElement('div');
if (data.label) {
label = node.appendChild(document.createElement('label'));
label.appendChild(document.createTextNode(data.label));
label.setAttribute('for', id);
}
subnode = node.appendChild(document.createElement('input'));
if (data.value) {
subnode.setAttribute('value', data.value);
}
subnode.setAttribute('name', data.name);
subnode.setAttribute('type', 'text');
if (data.size) {
subnode.setAttribute('size', data.size);
}
if (data.maxlength) {
subnode.setAttribute('maxlength', data.maxlength);
}
if (data.event) {
subnode.addEventListener('keyup', data.event, false);
}
if (data.remove) {
var remove = this.compute({
type: 'button',
label: 'remove',
event: function(e) {
var list = e.target.listnode;
var node = e.target.inputnode;
var more = e.target.morebutton;
list.removeChild(node);
--more.counter;
more.removeAttribute('disabled');
e.stopPropagation();
}
});
node.appendChild(remove[0]);
var removeButton = remove[1];
removeButton.inputnode = node;
removeButton.listnode = data.listnode;
removeButton.morebutton = data.morebutton;
}
break;
case 'hidden':
node = document.createElement('input');
node.setAttribute('type', 'hidden');
node.values = data.value;
node.setAttribute('value', data.value);
node.setAttribute('name', data.name);
break;
case 'header':
node = document.createElement('h5');
node.appendChild(document.createTextNode(data.label));
break;
case 'div':
node = document.createElement('div');
if (data.name) {
node.setAttribute('name', data.name);
}
if (data.label) {
if (!Array.isArray(data.label)) {
data.label = [ data.label ];
}
var result = document.createElement('span');
result.className = 'quickformDescription';
for (i = 0; i < data.label.length; ++i) {
if (typeof data.label[i] === 'string') {
result.appendChild(document.createTextNode(data.label[i]));
} else if (data.label[i] instanceof Element) {
result.appendChild(data.label[i]);
}
}
node.appendChild(result);
}
break;
case 'submit':
node = document.createElement('span');
childContainder = node.appendChild(document.createElement('input'));
childContainder.setAttribute('type', 'submit');
if (data.label) {
childContainder.setAttribute('value', data.label);
}
childContainder.setAttribute('name', data.name || 'submit');
if (data.disabled) {
childContainder.setAttribute('disabled', 'disabled');
}
break;
case 'button':
node = document.createElement('span');
childContainder = node.appendChild(document.createElement('input'));
childContainder.setAttribute('type', 'button');
if (data.label) {
childContainder.setAttribute('value', data.label);
}
childContainder.setAttribute('name', data.name);
if (data.disabled) {
childContainder.setAttribute('disabled', 'disabled');
}
if (data.event) {
childContainder.addEventListener('click', data.event, false);
}
break;
case 'textarea':
node = document.createElement('div');
node.setAttribute('id', 'div_' + id);
if (data.label) {
label = node.appendChild(document.createElement('h5'));
var labelElement = document.createElement('label');
labelElement.textContent = data.label;
labelElement.setAttribute('for', data.id || id);
label.appendChild(labelElement);
}
subnode = node.appendChild(document.createElement('textarea'));
subnode.setAttribute('name', data.name);
if (data.cols) {
subnode.setAttribute('cols', data.cols);
}
if (data.rows) {
subnode.setAttribute('rows', data.rows);
}
if (data.disabled) {
subnode.setAttribute('disabled', 'disabled');
}
if (data.required) {
subnode.setAttribute('required', 'required');
}
if (data.readonly) {
subnode.setAttribute('readonly', 'readonly');
}
if (data.value) {
subnode.value = data.value;
}
childContainder = subnode;
break;
default:
throw new Error('Morebits.quickForm: unknown element type ' + data.type.toString());
}
if (!childContainder) {
childContainder = node;
}
if (data.tooltip) {
Morebits.quickForm.element.generateTooltip(label || node, data);
}
if (data.extra) {
childContainder.extra = data.extra;
}
if (data.style) {
childContainder.setAttribute('style', data.style);
}
if (data.className) {
childContainder.className = childContainder.className ?
childContainder.className + ' ' + data.className :
data.className;
}
childContainder.setAttribute('id', data.id || id);
return [ node, childContainder ];
};
Morebits.quickForm.element.autoNWSW = function() {
return $(this).offset().top > ($(document).scrollTop() + ($(window).height() / 2)) ? 'sw' : 'nw';
};
/**
* Create a jquery.tipsy-based tooltip.
* @requires jquery.tipsy
* @param {HTMLElement} node - the HTML element beside which a tooltip is to be generated
* @param {Object} data - tooltip-related configuration data
*/
Morebits.quickForm.element.generateTooltip = function QuickFormElementGenerateTooltip(node, data) {
$('<span/>', {
'class': 'ui-icon ui-icon-help ui-icon-inline morebits-tooltip'
}).appendTo(node);
};
// Some utility methods for manipulating quickForms after their creation:
// (None of these work for "dyninput" type fields at present)
/**
* Returns all form elements with a given field name or ID
* @param {HTMLFormElement} form
* @param {string} fieldName - the name or id of the fields
* @returns {HTMLElement[]} - array of matching form elements
*/
Morebits.quickForm.getElements = function QuickFormGetElements(form, fieldName) {
var $form = $(form);
var $elements = $form.find('[name="' + fieldName + '"]');
if ($elements.length > 0) {
return $elements.toArray();
}
$elements = $form.find('#' + fieldName);
if ($elements.length > 0) {
return $elements.toArray();
}
return null;
};
/**
* Searches the array of elements for a checkbox or radio button with a certain
* `value` attribute, and returns the first such element. Returns null if not found.
* @param {HTMLInputElement[]} elementArray - array of checkbox or radio elements
* @param {string} value - value to search for
* @returns {HTMLInputElement}
*/
Morebits.quickForm.getCheckboxOrRadio = function QuickFormGetCheckboxOrRadio(elementArray, value) {
var found = $.grep(elementArray, function(el) {
return el.value === value;
});
if (found.length > 0) {
return found[0];
}
return null;
};
/**
* Returns the <div> containing the form element, or the form element itself
* May not work as expected on checkboxes or radios
* @param {HTMLElement} element
* @returns {HTMLElement}
*/
Morebits.quickForm.getElementContainer = function QuickFormGetElementContainer(element) {
// for divs, headings and fieldsets, the container is the element itself
if (element instanceof HTMLFieldSetElement || element instanceof HTMLDivElement ||
element instanceof HTMLHeadingElement) {
return element;
}
// for others, just return the parent node
return element.parentNode;
};
/**
* Gets the HTML element that contains the label of the given form element
* (mainly for internal use)
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @returns {HTMLElement}
*/
Morebits.quickForm.getElementLabelObject = function QuickFormGetElementLabelObject(element) {
// for buttons, divs and headers, the label is on the element itself
if (element.type === 'button' || element.type === 'submit' ||
element instanceof HTMLDivElement || element instanceof HTMLHeadingElement) {
return element;
// for fieldsets, the label is the child <legend> element
} else if (element instanceof HTMLFieldSetElement) {
return element.getElementsByTagName('legend')[0];
// for textareas, the label is the sibling <h5> element
} else if (element instanceof HTMLTextAreaElement) {
return element.parentNode.getElementsByTagName('h5')[0];
}
// for others, the label is the sibling <label> element
return element.parentNode.getElementsByTagName('label')[0];
};
/**
* Gets the label text of the element
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @returns {string}
*/
Morebits.quickForm.getElementLabel = function QuickFormGetElementLabel(element) {
var labelElement = Morebits.quickForm.getElementLabelObject(element);
if (!labelElement) {
return null;
}
return labelElement.firstChild.textContent;
};
/**
* Sets the label of the element to the given text
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @param {string} labelText
* @returns {boolean} true if succeeded, false if the label element is unavailable
*/
Morebits.quickForm.setElementLabel = function QuickFormSetElementLabel(element, labelText) {
var labelElement = Morebits.quickForm.getElementLabelObject(element);
if (!labelElement) {
return false;
}
labelElement.firstChild.textContent = labelText;
return true;
};
/**
* Stores the element's current label, and temporarily sets the label to the given text
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @param {string} temporaryLabelText
* @returns {boolean} true if succeeded, false if the label element is unavailable
*/
Morebits.quickForm.overrideElementLabel = function QuickFormOverrideElementLabel(element, temporaryLabelText) {
if (!element.hasAttribute('data-oldlabel')) {
element.setAttribute('data-oldlabel', Morebits.quickForm.getElementLabel(element));
}
return Morebits.quickForm.setElementLabel(element, temporaryLabelText);
};
/**
* Restores the label stored by overrideElementLabel
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @returns {boolean} true if succeeded, false if the label element is unavailable
*/
Morebits.quickForm.resetElementLabel = function QuickFormResetElementLabel(element) {
if (element.hasAttribute('data-oldlabel')) {
return Morebits.quickForm.setElementLabel(element, element.getAttribute('data-oldlabel'));
}
return null;
};
/**
* Shows or hides a form element plus its label and tooltip
* @param {(HTMLElement|jQuery|string)} element HTML/jQuery element, or jQuery selector string
* @param {boolean} [visibility] Skip this to toggle visibility
*/
Morebits.quickForm.setElementVisibility = function QuickFormSetElementVisibility(element, visibility) {
$(element).toggle(visibility);
};
/**
* Shows or hides the "question mark" icon (which displays the tooltip) next to a form element
* @param {(HTMLElement|jQuery)} element
* @param {boolean} [visibility] Skip this to toggle visibility
*/
Morebits.quickForm.setElementTooltipVisibility = function QuickFormSetElementTooltipVisibility(element, visibility) {
$(Morebits.quickForm.getElementContainer(element)).find('.morebits-tooltip').toggle(visibility);
};
/**
* **************** HTMLFormElement ****************
*/
/**
* Returns an array containing the values of elements with the given name, that has it's
* checked property set to true. (i.e. a checkbox or a radiobutton is checked), or select
* options that have selected set to true. (don't try to mix selects with radio/checkboxes,
* please)
* Type is optional and can specify if either radio or checkbox (for the event
* that both checkboxes and radiobuttons have the same name.
*
* XXX: Doesn't seem to work reliably across all browsers at the moment. -- see getChecked2
* in twinkleunlink.js, which is better
*/
HTMLFormElement.prototype.getChecked = function(name, type) {
var elements = this.elements[name];
if (!elements) {
// if the element doesn't exists, return null.
return null;
}
var return_array = [];
var i;
if (elements instanceof HTMLSelectElement) {
var options = elements.options;
for (i = 0; i < options.length; ++i) {
if (options[i].selected) {
if (options[i].values) {
return_array.push(options[i].values);
} else {
return_array.push(options[i].value);
}
}
}
} else if (elements instanceof HTMLInputElement) {
if (type && elements.type !== type) {
return [];
} else if (elements.checked) {
return [ elements.value ];
}
} else {
for (i = 0; i < elements.length; ++i) {
if (elements[i].checked) {
if (type && elements[i].type !== type) {
continue;
}
if (elements[i].values) {
return_array.push(elements[i].values);
} else {
return_array.push(elements[i].value);
}
}
}
}
return return_array;
};
/**
* getUnchecked:
* Does the same as getChecked above, but with unchecked elements.
*/
HTMLFormElement.prototype.getUnchecked = function(name, type) {
var elements = this.elements[name];
if (!elements) {
// if the element doesn't exists, return null.
return null;
}
var return_array = [];
var i;
if (elements instanceof HTMLSelectElement) {
var options = elements.options;
for (i = 0; i < options.length; ++i) {
if (!options[i].selected) {
if (options[i].values) {
return_array.push(options[i].values);
} else {
return_array.push(options[i].value);
}
}
}
} else if (elements instanceof HTMLInputElement) {
if (type && elements.type !== type) {
return [];
} else if (!elements.checked) {
return [ elements.value ];
}
} else {
for (i = 0; i < elements.length; ++i) {
if (!elements[i].checked) {
if (type && elements[i].type !== type) {
continue;
}
if (elements[i].values) {
return_array.push(elements[i].values);
} else {
return_array.push(elements[i].value);
}
}
}
}
return return_array;
};
/**
* **************** RegExp ****************
*
* Escapes a string to be used in a RegExp
* @param {string} text - string to be escaped
* @param {boolean} [space_fix=false] - Set true to replace spaces and underscores with `[ _]` as they are
* often equivalent
* @returns {string} - the escaped text
*/
RegExp.escape = function(text, space_fix) {
text = mw.util.escapeRegExp(text);
// Special MediaWiki escape - underscore/space are often equivalent
if (space_fix) {
text = text.replace(/ |_/g, '[_ ]');
}
return text;
};
/**
* **************** String; Morebits.string ****************
*/
Morebits.string = {
// Helper functions to change case of a string
toUpperCaseFirstChar: function(str) {
str = str.toString();
return str.substr(0, 1).toUpperCase() + str.substr(1);
},
toLowerCaseFirstChar: function(str) {
str = str.toString();
return str.substr(0, 1).toLowerCase() + str.substr(1);
},
/**
* Gives an array of substrings of `str` starting with `start` and
* ending with `end`, which is not in `skiplist`
* @param {string} str
* @param {string} start
* @param {string} end
* @param {(string[]|string)} [skiplist]
* @returns {String[]}
*/
splitWeightedByKeys: function(str, start, end, skiplist) {
if (start.length !== end.length) {
throw new Error('start marker and end marker must be of the same length');
}
var level = 0;
var initial = null;
var result = [];
if (!Array.isArray(skiplist)) {
if (skiplist === undefined) {
skiplist = [];
} else if (typeof skiplist === 'string') {
skiplist = [ skiplist ];
} else {
throw new Error('non-applicable skiplist parameter');
}
}
for (var i = 0; i < str.length; ++i) {
for (var j = 0; j < skiplist.length; ++j) {
if (str.substr(i, skiplist[j].length) === skiplist[j]) {
i += skiplist[j].length - 1;
continue;
}
}
if (str.substr(i, start.length) === start) {
if (initial === null) {
initial = i;
}
++level;
i += start.length - 1;
} else if (str.substr(i, end.length) === end) {
--level;
i += end.length - 1;
}
if (!level && initial !== null) {
result.push(str.substring(initial, i + 1));
initial = null;
}
}
return result;
},
/**
* Formats freeform "reason" (from a textarea) for deletion/other templates
* that are going to be substituted, (e.g. PROD, XFD, RPP)
* @param {string} str
* @returns {string}
*/
formatReasonText: function(str) {
var result = str.toString().trim();
var unbinder = new Morebits.unbinder(result);
unbinder.unbind('<no' + 'wiki>', '</no' + 'wiki>');
unbinder.content = unbinder.content.replace(/\|/g, '{{subst:!}}');
return unbinder.rebind();
},
/**
* Like `String.prototype.replace()`, but escapes any dollar signs in the replacement string.
* Useful when the the replacement string is arbitrary, such as a username or freeform user input,
* and could contain dollar signs.
* @param {string} string - text in which to replace
* @param {(string|RegExp)} pattern
* @param {string} replacement
* @returns {string}
*/
safeReplace: function morebitsStringSafeReplace(string, pattern, replacement) {
return string.replace(pattern, replacement.replace(/\$/g, '$$$$'));
}
};
/**
* **************** Morebits.array ****************
*/
Morebits.array = {
/**
* @returns {Array} a copy of the array with duplicates removed
*/
uniq: function(arr) {
if (!Array.isArray(arr)) {
throw 'A non-array object passed to Morebits.array.uniq';
}
var result = [];
for (var i = 0; i < arr.length; ++i) {
var current = arr[i];
if (result.indexOf(current) === -1) {
result.push(current);
}
}
return result;
},
/**
* @returns {Array} a copy of the array with the first instance of each value
* removed; subsequent instances of those values (duplicates) remain
*/
dups: function(arr) {
if (!Array.isArray(arr)) {
throw 'A non-array object passed to Morebits.array.dups';
}
var uniques = [];
var result = [];
for (var i = 0; i < arr.length; ++i) {
var current = arr[i];
if (uniques.indexOf(current) === -1) {
uniques.push(current);
} else {
result.push(current);
}
}
return result;
},
/**
* Break up an array into smaller arrays.
* @param {Array} arr
* @param {number} size - Size of each chunk (except the last, which could be different)
* @returns {Array} an array of these smaller arrays
*/
chunk: function(arr, size) {
if (!Array.isArray(arr)) {
throw 'A non-array object passed to Morebits.array.chunk';
}
if (typeof size !== 'number' || size <= 0) { // pretty impossible to do anything :)
return [ arr ]; // we return an array consisting of this array.
}
var result = [];
var current;
for (var i = 0; i < arr.length; ++i) {
if (i % size === 0) { // when 'i' is 0, this is always true, so we start by creating one.
current = [];
result.push(current);
}
current.push(arr[i]);
}
return result;
}
};
/**
* ************ Morebits.select2 ***************
* Utilities to enhance select2 menus
* See twinklewarn, twinklexfd, twinkleblock for sample usages
*/
Morebits.select2 = {
matchers: {
/**
* Custom matcher in which if the optgroup name matches, all options in that
* group are shown, like in jquery.chosen
*/
optgroupFull: function(params, data) {
var originalMatcher = $.fn.select2.defaults.defaults.matcher;
var result = originalMatcher(params, data);
if (result && params.term &&
data.text.toUpperCase().indexOf(params.term.toUpperCase()) !== -1) {
result.children = data.children;
}
return result;
},
/** Custom matcher that matches from the beginning of words only */
wordBeginning: function(params, data) {
var originalMatcher = $.fn.select2.defaults.defaults.matcher;
var result = originalMatcher(params, data);
if (!params.term || (result &&
new RegExp('\\b' + mw.util.escapeRegExp(params.term), 'i').test(result.text))) {
return result;
}
return null;
}
},
/** Underline matched part of options */
highlightSearchMatches: function(data) {
var searchTerm = Morebits.select2SearchQuery;
if (!searchTerm || data.loading) {
return data.text;
}
var idx = data.text.toUpperCase().indexOf(searchTerm.toUpperCase());
if (idx < 0) {
return data.text;
}
return $('<span>').append(
data.text.slice(0, idx),
$('<span>').css('text-decoration', 'underline').text(data.text.slice(idx, idx + searchTerm.length)),
data.text.slice(idx + searchTerm.length)
);
},
/** Intercept query as it is happening, for use in highlightSearchMatches */
queryInterceptor: function(params) {
Morebits.select2SearchQuery = params && params.term;
},
/**
* Open dropdown and begin search when the .select2-selection has focus and a key is pressed
* https://github.com/select2/select2/issues/3279#issuecomment-442524147
*/
autoStart: function(ev) {
if (ev.which < 48) {
return;
}
var target = $(ev.target).closest('.select2-container');
if (!target.length) {
return;
}
target = target.prev();
target.select2('open');
var search = target.data('select2').dropdown.$search ||
target.data('select2').selection.$search;
search.focus();
}
};
/**
* **************** Morebits.pageNameNorm ****************
* Stores a normalized version of the wgPageName variable (underscores converted to spaces).
* For queen/king/whatever and country!
*/
Morebits.pageNameNorm = mw.config.get('wgPageName').replace(/_/g, ' ');
/**
* *************** Morebits.pageNameRegex *****************
* For a page name 'Foo bar', returns the string '[Ff]oo bar'
* @param {string} pageName - page name without namespace
* @returns {string}
*/
Morebits.pageNameRegex = function(pageName) {
return '[' + pageName[0].toUpperCase() + pageName[0].toLowerCase() + ']' + pageName.slice(1);
};
/**
* **************** Morebits.unbinder ****************
* Used for temporarily hiding a part of a string while processing the rest of it.
*
* eg. var u = new Morebits.unbinder("Hello world <!-- world --> world");
* u.unbind('<!--','-->');
* u.content = u.content.replace(/world/g, 'earth');
* u.rebind(); // gives "Hello earth <!-- world --> earth"
*
* Text within the 'unbinded' part (in this case, the HTML comment) remains intact
* unbind() can be called multiple times to unbind multiple parts of the string.
*
* Used by Morebits.wikitext.page.commentOutImage
*/
/**
* @constructor
* @param {string} string
*/
Morebits.unbinder = function Unbinder(string) {
if (typeof string !== 'string') {
throw new Error('not a string');
}
this.content = string;
this.counter = 0;
this.history = {};
this.prefix = '%UNIQ::' + Math.random() + '::';
this.postfix = '::UNIQ%';
};
Morebits.unbinder.prototype = {
/**
* @param {string} prefix
* @param {string} postfix
*/
unbind: function UnbinderUnbind(prefix, postfix) {
var re = new RegExp(prefix + '([\\s\\S]*?)' + postfix, 'g');
this.content = this.content.replace(re, Morebits.unbinder.getCallback(this));
},
/** @returns {string} The output */
rebind: function UnbinderRebind() {
var content = this.content;
content.self = this;
for (var current in this.history) {
if (Object.prototype.hasOwnProperty.call(this.history, current)) {
content = content.replace(current, this.history[current]);
}
}
return content;
},
prefix: null, // %UNIQ::0.5955981644938324::
postfix: null, // ::UNIQ%
content: null, // string
counter: null, // 0++
history: null // {}
};
Morebits.unbinder.getCallback = function UnbinderGetCallback(self) {
return function UnbinderCallback(match) {
var current = self.prefix + self.counter + self.postfix;
self.history[current] = match;
++self.counter;
return current;
};
};
/**
* **************** Date ****************
* Helper functions to get the month as a string instead of a number
*
* @deprecated Since early 2020 in favor of Morebits.date (#814)
*/
Date.monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December' ];
Date.monthNamesAbbrev = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
Date.prototype.getUTCMonthName = function() {
console.warn("NOTE: Date prototypes from Twinkle's Morebits (such as getUTCMonthName) have been deprecated, use Morebits.date instead"); // eslint-disable-line no-console
return Date.monthNames[this.getUTCMonth()];
};
Date.prototype.getUTCMonthNameAbbrev = function() {
console.warn("NOTE: Date prototypes from Twinkle's Morebits (such as getUTCMonthNameAbbrev) have been deprecated, use Morebits.date instead"); // eslint-disable-line no-console
return Date.monthNamesAbbrev[this.getUTCMonth()];
};
/**
* **************** Morebits.date ****************
*/
/**
* @constructor
* Create a date object. MediaWiki timestamp format is also acceptable,
* in addition to everything that JS Date() accepts.
*/
Morebits.date = function() {
var args = Array.prototype.slice.call(arguments);
if (typeof args[0] === 'string') {
// Attempt to remove a comma and paren-wrapped timezone, to get MediaWiki timestamps to parse
// Firefox (at least in 75) seems to be okay with the comma, though
args[0] = args[0].replace(/(\d\d:\d\d),/, '$1').replace(/\(UTC\)/, 'UTC');
}
this._d = new (Function.prototype.bind.apply(Date, [Date].concat(args)));
};
Morebits.date.localeData = {
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
relativeTimes: {
thisDay: '[Today at] h:mm A',
prevDay: '[Yesterday at] h:mm A',
nextDay: '[Tomorrow at] h:mm A',
thisWeek: 'dddd [at] h:mm A',
pastWeek: '[Last] dddd [at] h:mm A',
other: 'YYYY-MM-DD'
}
};
// Allow native Date.prototype methods to be used on Morebits.date objects
Object.getOwnPropertyNames(Date.prototype).forEach(function(func) {
Morebits.date.prototype[func] = function() {
return this._d[func].apply(this._d, Array.prototype.slice.call(arguments));
};
});
$.extend(Morebits.date.prototype, {
isValid: function() {
return !isNaN(this.getTime());
},
/** @param {(Date|Morebits.date)} date */
isBefore: function(date) {
return this.getTime() < date.getTime();
},
isAfter: function(date) {
return this.getTime() > date.getTime();
},
/** @return {string} */
getUTCMonthName: function() {
return Morebits.date.localeData.months[this.getUTCMonth()];
},
getUTCMonthNameAbbrev: function() {
return Morebits.date.localeData.monthsShort[this.getUTCMonth()];
},
getMonthName: function() {
return Morebits.date.localeData.months[this.getMonth()];
},
getMonthNameAbbrev: function() {
return Morebits.date.localeData.monthsShort[this.getMonth()];
},
getUTCDayName: function() {
return Morebits.date.localeData.days[this.getUTCDay()];
},
getUTCDayNameAbbrev: function() {
return Morebits.date.localeData.daysShort[this.getUTCDay()];
},
getDayName: function() {
return Morebits.date.localeData.days[this.getDay()];
},
getDayNameAbbrev: function() {
return Morebits.date.localeData.daysShort[this.getDay()];
},
/**
* Add a given number of minutes, hours, days, months or years to the date.
* This is done in-place. The modified date object is also returned, allowing chaining.
* @param {number} number - should be an integer
* @param {string} unit
* @throws {Error} if invalid or unsupported unit is given
* @returns {Morebits.date}
*/
add: function(number, unit) {
// mapping time units with getter/setter function names
var unitMap = {
seconds: 'Seconds',
minutes: 'Minutes',
hours: 'Hours',
days: 'Date',
months: 'Month',
years: 'FullYear'
};
var unitNorm = unitMap[unit] || unitMap[unit + 's']; // so that both singular and plural forms work
if (unitNorm) {
this['set' + unitNorm](this['get' + unitNorm]() + number);
return this;
}
throw new Error('Invalid unit "' + unit + '": Only ' + Object.keys(unitMap).join(', ') + ' are allowed.');
},
/**
* Subtracts a given number of minutes, hours, days, months or years to the date.
* This is done in-place. The modified date object is also returned, allowing chaining.
* @param {number} number - should be an integer
* @param {string} unit
* @throws {Error} if invalid or unsupported unit is given
* @returns {Morebits.date}
*/
subtract: function(number, unit) {
return this.add(-number, unit);
},
/**
* Formats the date into a string per the given format string.
* Replacement syntax is a subset of that in moment.js.
* @param {string} formatstr
* @param {(string|number)} [zone=system] - 'system' (for browser-default time zone),
* 'utc' (for UTC), or specify a time zone as number of minutes past UTC.
* @returns {string}
*/
format: function(formatstr, zone) {
var udate = this;
// create a new date object that will contain the date to display as system time
if (zone === 'utc') {
udate = new Morebits.date(this.getTime()).add(this.getTimezoneOffset(), 'minutes');
} else if (typeof zone === 'number') {
// convert to utc, then add the utc offset given
udate = new Morebits.date(this.getTime()).add(this.getTimezoneOffset() + zone, 'minutes');
}
var pad = function(num) {
return num < 10 ? '0' + num : num;
};
var h24 = udate.getHours(), m = udate.getMinutes(), s = udate.getSeconds();
var D = udate.getDate(), M = udate.getMonth() + 1, Y = udate.getFullYear();
var h12 = h24 % 12 || 12, amOrPm = h24 >= 12 ? 'PM' : 'AM';
var replacementMap = {
'HH': pad(h24), 'H': h24, 'hh': pad(h12), 'h': h12, 'A': amOrPm,
'mm': pad(m), 'm': m,
'ss': pad(s), 's': s,
'dddd': udate.getDayName(), 'ddd': udate.getDayNameAbbrev(), 'd': udate.getDay(),
'DD': pad(D), 'D': D,
'MMMM': udate.getMonthName(), 'MMM': udate.getMonthNameAbbrev(), 'MM': pad(M), 'M': M,
'YYYY': Y, 'YY': pad(Y % 100), 'Y': Y
};
var unbinder = new Morebits.unbinder(formatstr); // escape stuff between [...]
unbinder.unbind('\\[', '\\]');
unbinder.content = unbinder.content.replace(
/* Regex notes:
* d(d{2,3})? matches exactly 1, 3 or 4 occurrences of 'd' ('dd' is treated as a double match of 'd')
* Y{1,2}(Y{2})? matches exactly 1, 2 or 4 occurrences of 'Y'
*/
/H{1,2}|h{1,2}|m{1,2}|s{1,2}|d(d{2,3})?|D{1,2}|M{1,4}|Y{1,2}(Y{2})?|A/g,
function(match) {
return replacementMap[match];
}
);
return unbinder.rebind().replace(/\[(.*?)\]/g, '$1');
},
/**
* Gives a readable relative time string such as "Yesterday at 6:43 PM" or "Last Thursday at 11:45 AM".
* Similar to calendar in moment.js, but with time zone support.
* @param {(string|number)} [zone=system] - 'system' (for browser-default time zone),
* 'utc' (for UTC), or specify a time zone as number of minutes past UTC
* @returns {string}
*/
calendar: function(zone) {
// Zero out the hours, minutes, seconds and milliseconds - keeping only the date;
// find the difference. Note that setHours() returns the same thing as getTime().
var dateDiff = (new Date().setHours(0, 0, 0, 0) -
new Date(this).setHours(0, 0, 0, 0)) / 8.64e7;
switch (true) {
case dateDiff === 0:
return this.format(Morebits.date.localeData.relativeTimes.thisDay, zone);
case dateDiff === 1:
return this.format(Morebits.date.localeData.relativeTimes.prevDay, zone);
case dateDiff > 0 && dateDiff < 7:
return this.format(Morebits.date.localeData.relativeTimes.pastWeek, zone);
case dateDiff === -1:
return this.format(Morebits.date.localeData.relativeTimes.nextDay, zone);
case dateDiff < 0 && dateDiff > -7:
return this.format(Morebits.date.localeData.relativeTimes.thisWeek, zone);
default:
return this.format(Morebits.date.localeData.relativeTimes.other, zone);
}
},
/**
* @returns {RegExp} that matches wikitext section titles such as ==December 2019== or
* === Jan 2018 ===
*/
monthHeaderRegex: function() {
return new RegExp('^==+\\s*(?:' + this.getUTCMonthName() + '|' + this.getUTCMonthNameAbbrev() +
')\\s+' + this.getUTCFullYear() + '\\s*==+', 'mg');
},
/**
* Creates a wikitext section header with the month and year.
* @param {number} [level=2] - Header level (default 2)
* @returns {string}
*/
monthHeader: function(level) {
level = level || 2;
var header = Array(level + 1).join('='); // String.prototype.repeat not supported in IE 11
return header + ' ' + this.getUTCMonthName() + ' ' + this.getUTCFullYear() + ' ' + header;
}
});
/**
* **************** Morebits.wiki ****************
* Various objects for wiki editing and API access
*/
Morebits.wiki = {};
/**
* Determines whether the current page is a redirect or soft redirect
* (fails to detect soft redirects on edit, history, etc. pages)
* @returns {boolean}
*/
Morebits.wiki.isPageRedirect = function wikipediaIsPageRedirect() {
return !!(mw.config.get('wgIsRedirect') || document.getElementById('softredirect'));
};
/**
* **************** Morebits.wiki.actionCompleted ****************
*
* Use of Morebits.wiki.actionCompleted():
* Every call to Morebits.wiki.api.post() results in the dispatch of
* an asynchronous callback. Each callback can in turn
* make an additional call to Morebits.wiki.api.post() to continue a
* processing sequence. At the conclusion of the final callback
* of a processing sequence, it is not possible to simply return to the
* original caller because there is no call stack leading back to
* the original context. Instead, Morebits.wiki.actionCompleted.event() is
* called to display the result to the user and to perform an optional
* page redirect.
*
* The determination of when to call Morebits.wiki.actionCompleted.event()
* is managed through the globals Morebits.wiki.numberOfActionsLeft and
* Morebits.wiki.nbrOfCheckpointsLeft. Morebits.wiki.numberOfActionsLeft is
* incremented at the start of every Morebits.wiki.api call and decremented
* after the completion of a callback function. If a callback function
* does not create a new Morebits.wiki.api object before exiting, it is the
* final step in the processing chain and Morebits.wiki.actionCompleted.event()
* will then be called.
*
* Optionally, callers may use Morebits.wiki.addCheckpoint() to indicate that
* processing is not complete upon the conclusion of the final callback function.
* This is used for batch operations. The end of a batch is signaled by calling
* Morebits.wiki.removeCheckpoint().
*/
Morebits.wiki.numberOfActionsLeft = 0;
Morebits.wiki.nbrOfCheckpointsLeft = 0;
Morebits.wiki.actionCompleted = function(self) {
if (--Morebits.wiki.numberOfActionsLeft <= 0 && Morebits.wiki.nbrOfCheckpointsLeft <= 0) {
Morebits.wiki.actionCompleted.event(self);
}
};
// Change per action wanted
Morebits.wiki.actionCompleted.event = function() {
if (Morebits.wiki.actionCompleted.notice) {
Morebits.status.actionCompleted(Morebits.wiki.actionCompleted.notice);
}
if (Morebits.wiki.actionCompleted.redirect) {
// if it isn't a URL, make it one. TODO: This breaks on the articles 'http://', 'ftp://', and similar ones.
if (!(/^\w+:\/\//).test(Morebits.wiki.actionCompleted.redirect)) {
Morebits.wiki.actionCompleted.redirect = mw.util.getUrl(Morebits.wiki.actionCompleted.redirect);
if (Morebits.wiki.actionCompleted.followRedirect === false) {
Morebits.wiki.actionCompleted.redirect += '?redirect=no';
}
}
window.setTimeout(function() {
window.location = Morebits.wiki.actionCompleted.redirect;
}, Morebits.wiki.actionCompleted.timeOut);
}
};
Morebits.wiki.actionCompleted.timeOut = typeof window.wpActionCompletedTimeOut === 'undefined' ? 5000 : window.wpActionCompletedTimeOut;
Morebits.wiki.actionCompleted.redirect = null;
Morebits.wiki.actionCompleted.notice = null;
Morebits.wiki.addCheckpoint = function() {
++Morebits.wiki.nbrOfCheckpointsLeft;
};
Morebits.wiki.removeCheckpoint = function() {
if (--Morebits.wiki.nbrOfCheckpointsLeft <= 0 && Morebits.wiki.numberOfActionsLeft <= 0) {
Morebits.wiki.actionCompleted.event();
}
};
/**
* **************** Morebits.wiki.api ****************
* An easy way to talk to the MediaWiki API.
*/
/**
* In new code, the use of the last 3 parameters should be avoided, instead use setStatusElement() to bind the
* status element (if needed) and use .then() or .catch() on the promise returned by post(), rather than specify
* the onSuccess or onFailure callbacks.
* @constructor
* @param {string} currentAction - The current action (required)
* @param {Object} query - The query (required)
* @param {Function} [onSuccess] - The function to call when request gotten
* @param {Morebits.status} [statusElement] - A Morebits.status object to use for status messages (optional)
* @param {Function} [onError] - The function to call if an error occurs (optional)
*/
Morebits.wiki.api = function(currentAction, query, onSuccess, statusElement, onError) {
this.currentAction = currentAction;
this.query = query;
this.query.assert = 'user';
this.onSuccess = onSuccess;
this.onError = onError;
if (statusElement) {
this.setStatusElement(statusElement);
} else {
this.statelem = new Morebits.status(currentAction);
}
if (!query.format) {
this.query.format = 'xml';
} else if (['xml', 'json'].indexOf(query.format) === -1) {
this.statelem.error('Invalid API format: only xml and json are supported.');
}
};
Morebits.wiki.api.prototype = {
currentAction: '',
onSuccess: null,
onError: null,
parent: window, // use global context if there is no parent object
query: null,
response: null,
responseXML: null, // use `response` instead; retained for backwards compatibility
statelem: null, // this non-standard name kept for backwards compatibility
statusText: null, // result received from the API, normally "success" or "error"
errorCode: null, // short text error code, if any, as documented in the MediaWiki API
errorText: null, // full error description, if any
/**
* Keep track of parent object for callbacks
* @param {*} parent
*/
setParent: function(parent) {
this.parent = parent;
},
/** @param {Morebits.status} statusElement */
setStatusElement: function(statusElement) {
this.statelem = statusElement;
this.statelem.status(this.currentAction);
},
/**
* Carries out the request.
* @param {Object} callerAjaxParameters Do not specify a parameter unless you really
* really want to give jQuery some extra parameters
* @returns {promise} - a jQuery promise object that is resolved or rejected with the api object.
*/
post: function(callerAjaxParameters) {
++Morebits.wiki.numberOfActionsLeft;
var queryString = $.map(this.query, function(val, i) {
if (Array.isArray(val)) {
return encodeURIComponent(i) + '=' + val.map(encodeURIComponent).join('|');
} else if (val !== undefined) {
return encodeURIComponent(i) + '=' + encodeURIComponent(val);
}
}).join('&').replace(/^(.*?)(\btoken=[^&]*)&(.*)/, '$1$3&$2');
// token should always be the last item in the query string (bug TW-B-0013)
var ajaxparams = $.extend({}, {
context: this,
type: 'POST',
url: mw.util.wikiScript('api'),
data: queryString,
dataType: 'xml',
headers: {
'Api-User-Agent': morebitsWikiApiUserAgent
}
}, callerAjaxParameters);
return $.ajax(ajaxparams).then(
function onAPIsuccess(response, statusText) {
this.statusText = statusText;
this.response = this.responseXML = response;
if (this.query.format === 'json') {
this.errorCode = response.error && response.error.code;
this.errorText = response.error && response.error.info;
} else {
this.errorCode = $(response).find('error').attr('code');
this.errorText = $(response).find('error').attr('info');
}
if (typeof this.errorCode === 'string') {
// the API didn't like what we told it, e.g., bad edit token or an error creating a page
return this.returnError();
}
// invoke success callback if one was supplied
if (this.onSuccess) {
// set the callback context to this.parent for new code and supply the API object
// as the first argument to the callback (for legacy code)
this.onSuccess.call(this.parent, this);
} else {
this.statelem.info('انجام شد'); // localized
}
Morebits.wiki.actionCompleted();
return $.Deferred().resolveWith(this.parent, [this]);
},
// only network and server errors reach here - complaints from the API itself are caught in success()
function onAPIfailure(jqXHR, statusText, errorThrown) {
this.statusText = statusText;
this.errorThrown = errorThrown; // frequently undefined
this.errorText = statusText + ' "' + jqXHR.statusText + '" occurred while contacting the API.';
return this.returnError();
}
);
},
returnError: function() {
if (this.errorCode === 'badtoken') {
// TODO: automatically retry after getting a new token
this.statelem.error('Invalid token. Refresh the page and try again');
} else {
this.statelem.error(this.errorText);
}
// invoke failure callback if one was supplied
if (this.onError) {
// set the callback context to this.parent for new code and supply the API object
// as the first argument to the callback for legacy code
this.onError.call(this.parent, this);
}
// don't complete the action so that the error remains displayed
return $.Deferred().rejectWith(this.parent, [this]);
},
getStatusElement: function() {
return this.statelem;
},
getErrorCode: function() {
return this.errorCode;
},
getErrorText: function() {
return this.errorText;
},
getXML: function() { // retained for backwards compatibility, use getResponse() instead
return this.responseXML;
},
getResponse: function() {
return this.response;
}
};
// Custom user agent header, used by WMF for server-side logging
// See https://lists.wikimedia.org/pipermail/mediawiki-api-announce/2014-November/000075.html
var morebitsWikiApiUserAgent = 'morebits.js/2.0 ([[w:WT:TW]])';
/**
* Sets the custom user agent header
* @param {string} ua User agent
*/
Morebits.wiki.api.setApiUserAgent = function(ua) {
morebitsWikiApiUserAgent = (ua ? ua + ' ' : '') + 'morebits.js/2.0 ([[w:WT:TW]])';
};
/**
* **************** Morebits.wiki.page ****************
* Uses the MediaWiki API to load a page and optionally edit it, move it, etc.
*
* Callers are not permitted to directly access the properties of this class!
* All property access is through the appropriate get___() or set___() method.
*
* Callers should set Morebits.wiki.actionCompleted.notice and Morebits.wiki.actionCompleted.redirect
* before the first call to Morebits.wiki.page.load().
*
* Each of the callback functions takes one parameter, which is a
* reference to the Morebits.wiki.page object that registered the callback.
* Callback functions may invoke any Morebits.wiki.page prototype method using this reference.
*
*
* HIGHLIGHTS:
*
* Constructor: Morebits.wiki.page(pageName, currentAction)
* pageName - the name of the page, prefixed by the namespace (if any)
* (for the current page, use mw.config.get('wgPageName'))
* currentAction - a string describing the action about to be undertaken (optional)
*
* onSuccess and onFailure are callback functions called when the operation is a success or failure
* if enclosed in [brackets], it indicates that it is optional
*
* load(onSuccess, [onFailure]): Loads the text for the page
*
* getPageText(): returns a string containing the text of the page after a successful load()
*
* save([onSuccess], [onFailure]): Saves the text set via setPageText() for the page.
* Must be preceded by calling load().
* Warning: Calling save() can result in additional calls to the previous load() callbacks to
* recover from edit conflicts!
* In this case, callers must make the same edit to the new pageText and reinvoke save().
* This behavior can be disabled with setMaxConflictRetries(0).
*
* append([onSuccess], [onFailure]): Adds the text provided via setAppendText() to the end of
* the page. Does not require calling load() first.
*
* prepend([onSuccess], [onFailure]): Adds the text provided via setPrependText() to the start
* of the page. Does not require calling load() first.
*
* move([onSuccess], [onFailure]): Moves a page to another title
*
* patrol(): Patrols a page; ignores errors
*
* triage(): Marks page as reviewed using PageTriage, which implies patrolled; ignores most errors
*
* deletePage([onSuccess], [onFailure]): Deletes a page (for admins only)
*
* undeletePage([onSuccess], [onFailure]): Undeletes a page (for admins only)
*
* protect([onSuccess], [onFailure]): Protects a page
*
* getPageName(): returns a string containing the name of the loaded page, including the namespace
*
* setPageText(pageText) sets the updated page text that will be saved when save() is called
*
* setAppendText(appendText) sets the text that will be appended to the page when append() is called
*
* setPrependText(prependText) sets the text that will be prepended to the page when prepend() is called
*
* setCallbackParameters(callbackParameters)
* callbackParameters - an object for use in a callback function
*
* getCallbackParameters(): returns the object previous set by setCallbackParameters()
*
* Callback notes: callbackParameters is for use by the caller only. The parameters
* allow a caller to pass the proper context into its callback function.
* Callers must ensure that any changes to the callbackParameters object
* within a load() callback still permit a proper re-entry into the
* load() callback if an edit conflict is detected upon calling save().
*
* getStatusElement(): returns the Status element created by the constructor
*
* exists(): returns true if the page existed on the wiki when it was last loaded
*
* getCurrentID(): returns a string containing the current revision ID of the page
*
* lookupCreation(onSuccess): Retrieves the username and timestamp of page creation
* onSuccess - callback function which is called when the username and timestamp
* are found within the callback.
* The username can be retrieved using the getCreator() function;
* the timestamp can be retrieved using the getCreationTimestamp() function
*
* getCreator(): returns the user who created the page following lookupCreation()
*
* getCreationTimestamp(): returns an ISOString timestamp of page creation following lookupCreation()
*
*/
/**
* Call sequence for common operations (optional final user callbacks not shown):
*
* Edit current contents of a page (no edit conflict):
* .load(userTextEditCallback) -> ctx.loadApi.post() -> ctx.loadApi.post.success() ->
* ctx.fnLoadSuccess() -> userTextEditCallback() -> .save() ->
* ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()
*
* Edit current contents of a page (with edit conflict):
* .load(userTextEditCallback) -> ctx.loadApi.post() -> ctx.loadApi.post.success() ->
* ctx.fnLoadSuccess() -> userTextEditCallback() -> .save() ->
* ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveError() ->
* ctx.loadApi.post() -> ctx.loadApi.post.success() ->
* ctx.fnLoadSuccess() -> userTextEditCallback() -> .save() ->
* ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()
*
* Append to a page (similar for prepend):
* .append() -> ctx.loadApi.post() -> ctx.loadApi.post.success() ->
* ctx.fnLoadSuccess() -> ctx.fnAutoSave() -> .save() ->
* ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()
*
* Notes:
* 1. All functions following Morebits.wiki.api.post() are invoked asynchronously
* from the jQuery AJAX library.
* 2. The sequence for append/prepend could be slightly shortened, but it would require
* significant duplication of code for little benefit.
*/
/**
* @constructor
* @param {string} pageName The name of the page, prefixed by the namespace (if any)
* For the current page, use mw.config.get('wgPageName')
* @param {string} [currentAction] A string describing the action about to be undertaken (optional)
*/
Morebits.wiki.page = function(pageName, currentAction) {
if (!currentAction) {
currentAction = 'باز کردن صفحهٔ «' + pageName + '»'; // localized
}
/**
* Private context variables
*
* This context is not visible to the outside, thus all the data here
* must be accessed via getter and setter functions.
*/
var ctx = {
// backing fields for public properties
pageName: pageName,
pageExists: false,
editSummary: null,
callbackParameters: null,
statusElement: new Morebits.status(currentAction),
// - edit
pageText: null,
editMode: 'all', // save() replaces entire contents of the page by default
appendText: null, // can't reuse pageText for this because pageText is needed to follow a redirect
prependText: null, // can't reuse pageText for this because pageText is needed to follow a redirect
createOption: null,
minorEdit: false,
botEdit: false,
pageSection: null,
maxConflictRetries: 2,
maxRetries: 2,
followRedirect: false,
watchlistOption: 'nochange',
creator: null,
timestamp: null,
// - revert
revertOldID: null,
// - move
moveDestination: null,
moveTalkPage: false,
moveSubpages: false,
moveSuppressRedirect: false,
// - protect
protectEdit: null,
protectMove: null,
protectCreate: null,
protectCascade: false,
// - creation lookup
lookupNonRedirectCreator: false,
// - stabilize (FlaggedRevs)
flaggedRevs: null,
// internal status
pageLoaded: false,
csrfToken: null,
loadTime: null,
lastEditTime: null,
pageID: null,
revertCurID: null,
revertUser: null,
fullyProtected: false,
suppressProtectWarning: false,
conflictRetries: 0,
retries: 0,
// callbacks
onLoadSuccess: null,
onLoadFailure: null,
onSaveSuccess: null,
onSaveFailure: null,
onLookupCreationSuccess: null,
onMoveSuccess: null,
onMoveFailure: null,
onDeleteSuccess: null,
onDeleteFailure: null,
onUndeleteSuccess: null,
onUndeleteFailure: null,
onProtectSuccess: null,
onProtectFailure: null,
onStabilizeSuccess: null,
onStabilizeFailure: null,
// internal objects
loadQuery: null,
loadApi: null,
saveApi: null,
lookupCreationApi: null,
moveApi: null,
moveProcessApi: null,
patrolApi: null,
patrolProcessApi: null,
triageApi: null,
triageProcessApi: null,
deleteApi: null,
deleteProcessApi: null,
undeleteApi: null,
undeleteProcessApi: null,
protectApi: null,
protectProcessApi: null,
stabilizeApi: null,
stabilizeProcessApi: null
};
var emptyFunction = function() { };
/**
* Loads the text for the page
* @param {Function} onSuccess - callback function which is called when the load has succeeded
* @param {Function} [onFailure] - callback function which is called when the load fails (optional)
*/
this.load = function(onSuccess, onFailure) {
ctx.onLoadSuccess = onSuccess;
ctx.onLoadFailure = onFailure || emptyFunction;
// Need to be able to do something after the page loads
if (!onSuccess) {
ctx.statusElement.error('Internal error: no onSuccess callback provided to load()!');
ctx.onLoadFailure(this);
return;
}
ctx.loadQuery = {
action: 'query',
prop: 'info|revisions',
curtimestamp: '',
meta: 'tokens',
type: 'csrf',
titles: ctx.pageName
// don't need rvlimit=1 because we don't need rvstartid here and only one actual rev is returned by default
};
if (ctx.editMode === 'all') {
ctx.loadQuery.rvprop = 'content|timestamp'; // get the page content at the same time, if needed
} else if (ctx.editMode === 'revert') {
ctx.loadQuery.rvprop = 'timestamp';
ctx.loadQuery.rvlimit = 1;
ctx.loadQuery.rvstartid = ctx.revertOldID;
}
if (ctx.followRedirect) {
ctx.loadQuery.redirects = ''; // follow all redirects
}
if (typeof ctx.pageSection === 'number') {
ctx.loadQuery.rvsection = ctx.pageSection;
}
if (Morebits.userIsSysop) {
ctx.loadQuery.inprop = 'protection';
}
ctx.loadApi = new Morebits.wiki.api('Retrieving page...', ctx.loadQuery, fnLoadSuccess, ctx.statusElement, ctx.onLoadFailure);
ctx.loadApi.setParent(this);
ctx.loadApi.post();
};
/**
* Saves the text for the page to Wikipedia
* Must be preceded by successfully calling load().
*
* Warning: Calling save() can result in additional calls to the previous load() callbacks
* to recover from edit conflicts!
* In this case, callers must make the same edit to the new pageText and reinvoke save().
* This behavior can be disabled with setMaxConflictRetries(0).
* @param {Function} [onSuccess] - callback function which is called when the save has
* succeeded (optional)
* @param {Function} [onFailure] - callback function which is called when the save fails
* (optional)
*/
this.save = function(onSuccess, onFailure) {
ctx.onSaveSuccess = onSuccess;
ctx.onSaveFailure = onFailure || emptyFunction;
// are we getting our editing token from mw.user.tokens?
var canUseMwUserToken = fnCanUseMwUserToken('edit');
if (!ctx.pageLoaded && !canUseMwUserToken) {
ctx.statusElement.error('Internal error: attempt to save a page that has not been loaded!');
ctx.onSaveFailure(this);
return;
}
if (!ctx.editSummary) {
ctx.statusElement.error('Internal error: edit summary not set before save!');
ctx.onSaveFailure(this);
return;
}
// shouldn't happen if canUseMwUserToken === true
if (ctx.fullyProtected && !ctx.suppressProtectWarning &&
!confirm('You are about to make an edit to the fully protected page "' + ctx.pageName +
(ctx.fullyProtected === 'infinity' ? '" (protected indefinitely)' : '" (protection expiring ' + new Morebits.date(ctx.fullyProtected).calendar('utc') + ' (UTC))') +
'. \n\nClick OK to proceed with the edit, or Cancel to skip this edit.')) {
ctx.statusElement.error('Edit to fully protected page was aborted.');
ctx.onSaveFailure(this);
return;
}
ctx.retries = 0;
var query = {
action: 'edit',
title: ctx.pageName,
summary: ctx.editSummary,
token: canUseMwUserToken ? mw.user.tokens.get('csrfToken') : ctx.csrfToken,
watchlist: ctx.watchlistOption
};
if (typeof ctx.pageSection === 'number') {
query.section = ctx.pageSection;
}
// Set minor edit attribute. If these parameters are present with any value, it is interpreted as true
if (ctx.minorEdit) {
query.minor = true;
} else {
query.notminor = true; // force Twinkle config to override user preference setting for "all edits are minor"
}
// Set bot edit attribute. If this paramter is present with any value, it is interpreted as true
if (ctx.botEdit) {
query.bot = true;
}
switch (ctx.editMode) {
case 'append':
query.appendtext = ctx.appendText; // use mode to append to current page contents
break;
case 'prepend':
query.prependtext = ctx.prependText; // use mode to prepend to current page contents
break;
case 'revert':
query.undo = ctx.revertCurID;
query.undoafter = ctx.revertOldID;
if (ctx.lastEditTime) {
query.basetimestamp = ctx.lastEditTime; // check that page hasn't been edited since it was loaded
}
query.starttimestamp = ctx.loadTime; // check that page hasn't been deleted since it was loaded (don't recreate bad stuff)
break;
default: // 'all'
query.text = ctx.pageText; // replace entire contents of the page
if (ctx.lastEditTime) {
query.basetimestamp = ctx.lastEditTime; // check that page hasn't been edited since it was loaded
}
query.starttimestamp = ctx.loadTime; // check that page hasn't been deleted since it was loaded (don't recreate bad stuff)
break;
}
if (['recreate', 'createonly', 'nocreate'].indexOf(ctx.createOption) !== -1) {
query[ctx.createOption] = '';
}
if (canUseMwUserToken && ctx.followRedirect) {
query.redirect = true;
}
ctx.saveApi = new Morebits.wiki.api('Saving page...', query, fnSaveSuccess, ctx.statusElement, fnSaveError);
ctx.saveApi.setParent(this);
ctx.saveApi.post();
};
/**
* Adds the text provided via setAppendText() to the end of the page.
* Does not require calling load() first.
* @param {Function} [onSuccess] - callback function which is called when the method has succeeded (optional)
* @param {Function} [onFailure] - callback function which is called when the method fails (optional)
*/
this.append = function(onSuccess, onFailure) {
ctx.editMode = 'append';
if (fnCanUseMwUserToken('edit')) {
this.save(onSuccess, onFailure);
} else {
ctx.onSaveSuccess = onSuccess;
ctx.onSaveFailure = onFailure || emptyFunction;
this.load(fnAutoSave, ctx.onSaveFailure);
}
};
/**
* Adds the text provided via setPrependText() to the start of the page.
* Does not require calling load() first.
* @param {Function} [onSuccess] - callback function which is called when the method has succeeded (optional)
* @param {Function} [onFailure] - callback function which is called when the method fails (optional)
*/
this.prepend = function(onSuccess, onFailure) {
ctx.editMode = 'prepend';
if (fnCanUseMwUserToken('edit')) {
this.save(onSuccess, onFailure);
} else {
ctx.onSaveSuccess = onSuccess;
ctx.onSaveFailure = onFailure || emptyFunction;
this.load(fnAutoSave, ctx.onSaveFailure);
}
};
/** @returns {string} string containing the name of the loaded page, including the namespace */
this.getPageName = function() {
return ctx.pageName;
};
/** @returns {string} string containing the text of the page after a successful load() */
this.getPageText = function() {
return ctx.pageText;
};
/** @param {string} pageText - updated page text that will be saved when save() is called */
this.setPageText = function(pageText) {
ctx.editMode = 'all';
ctx.pageText = pageText;
};
/** @param {string} appendText - text that will be appended to the page when append() is called */
this.setAppendText = function(appendText) {
ctx.editMode = 'append';
ctx.appendText = appendText;
};
/** @param {string} prependText - text that will be prepended to the page when prepend() is called */
this.setPrependText = function(prependText) {
ctx.editMode = 'prepend';
ctx.prependText = prependText;
};
// Edit-related setter methods:
/** @param {string} summary - text of the edit summary that will be used when save() is called */
this.setEditSummary = function(summary) {
ctx.editSummary = summary;
};
/**
* @param {string} createOption - can take the following four values:
* `recreate` - create the page if it does not exist, or edit it if it exists.
* `createonly` - create the page if it does not exist, but return an error if it
* already exists.
* `nocreate` - don't create the page, only edit it if it already exists.
* null - create the page if it does not exist, unless it was deleted in the moment
* between loading the page and saving the edit (default)
*
*/
this.setCreateOption = function(createOption) {
ctx.createOption = createOption;
};
/** @param {boolean} minorEdit - set true to mark the edit as a minor edit. */
this.setMinorEdit = function(minorEdit) {
ctx.minorEdit = minorEdit;
};
/** @param {boolean} botEdit - set true to mark the edit as a bot edit */
this.setBotEdit = function(botEdit) {
ctx.botEdit = botEdit;
};
/**
* @param {number} pageSection - integer specifying the section number to load or save.
* If specified as `null`, the entire page will be retrieved.
*/
this.setPageSection = function(pageSection) {
ctx.pageSection = pageSection;
};
/**
* @param {number} maxConflictRetries - number of retries for save errors involving an edit conflict or
* loss of token. Default: 2
*/
this.setMaxConflictRetries = function(maxConflictRetries) {
ctx.maxConflictRetries = maxConflictRetries;
};
/**
* @param {number} maxRetries - number of retries for save errors not involving an edit conflict or
* loss of token. Default: 2
*/
this.setMaxRetries = function(maxRetries) {
ctx.maxRetries = maxRetries;
};
/**
* @param {boolean} watchlistOption
* True - page will be added to the user's watchlist when save() is called
* False - watchlist status of the page will not be changed (default)
*/
this.setWatchlist = function(watchlistOption) {
if (watchlistOption) {
ctx.watchlistOption = 'watch';
} else {
ctx.watchlistOption = 'nochange';
}
};
/**
* @param {boolean} watchlistOption
* True - page watchlist status will be set based on the user's
* preference settings when save() is called.
* False - watchlist status of the page will not be changed (default)
*
* Watchlist notes:
* 1. The MediaWiki API value of 'unwatch', which explicitly removes the page from the
* user's watchlist, is not used.
* 2. If both setWatchlist() and setWatchlistFromPreferences() are called,
* the last call takes priority.
* 3. Twinkle modules should use the appropriate preference to set the watchlist options.
* 4. Most Twinkle modules use setWatchlist().
* setWatchlistFromPreferences() is only needed for the few Twinkle watchlist preferences
* that accept a string value of 'default'.
*/
this.setWatchlistFromPreferences = function(watchlistOption) {
if (watchlistOption) {
ctx.watchlistOption = 'preferences';
} else {
ctx.watchlistOption = 'nochange';
}
};
/**
* @param {boolean} followRedirect
* true - a maximum of one redirect will be followed.
* In the event of a redirect, a message is displayed to the user and
* the redirect target can be retrieved with getPageName().
* false - the requested pageName will be used without regard to any redirect (default).
*/
this.setFollowRedirect = function(followRedirect) {
if (ctx.pageLoaded) {
ctx.statusElement.error('Internal error: cannot change redirect setting after the page has been loaded!');
return;
}
ctx.followRedirect = followRedirect;
};
// lookup-creation setter function
/**
* @param {boolean} flag - if set true, the author and timestamp of the first non-redirect
* version of the page is retrieved.
*
* Warning:
* 1. If there are no revisions among the first 50 that are non-redirects, or if there are
* less 50 revisions and all are redirects, the original creation is retrived.
* 2. Revisions that the user is not privileged to access (revdeled/suppressed) will be treated
* as non-redirects.
* 3. Must not be used when the page has a non-wikitext contentmodel
* such as Modulespace Lua or user JavaScript/CSS
*/
this.setLookupNonRedirectCreator = function(flag) {
ctx.lookupNonRedirectCreator = flag;
};
// Move-related setter functions
/** @param {string} destination */
this.setMoveDestination = function(destination) {
ctx.moveDestination = destination;
};
/** @param {boolean} flag */
this.setMoveTalkPage = function(flag) {
ctx.moveTalkPage = !!flag;
};
/** @param {boolean} flag */
this.setMoveSubpages = function(flag) {
ctx.moveSubpages = !!flag;
};
/** @param {boolean} flag */
this.setMoveSuppressRedirect = function(flag) {
ctx.moveSuppressRedirect = !!flag;
};
// Protect-related setter functions
this.setEditProtection = function(level, expiry) {
ctx.protectEdit = { level: level, expiry: expiry };
};
this.setMoveProtection = function(level, expiry) {
ctx.protectMove = { level: level, expiry: expiry };
};
this.setCreateProtection = function(level, expiry) {
ctx.protectCreate = { level: level, expiry: expiry };
};
this.setCascadingProtection = function(flag) {
ctx.protectCascade = !!flag;
};
this.suppressProtectWarning = function() {
ctx.suppressProtectWarning = true;
};
// Revert-related getters/setters:
this.setOldID = function(oldID) {
ctx.revertOldID = oldID;
};
/** @returns {string} string containing the current revision ID of the page */
this.getCurrentID = function() {
return ctx.revertCurID;
};
/** @returns {string} last editor of the page */
this.getRevisionUser = function() {
return ctx.revertUser;
};
/** @returns {string} ISO 8601 timestamp at which the page was last edited. */
this.getLastEditTime = function() {
return ctx.lastEditTime;
};
// Miscellaneous getters/setters:
/**
* `callbackParameters` - an object for use in a callback function
*
* Callback notes: callbackParameters is for use by the caller only. The parameters
* allow a caller to pass the proper context into its callback function.
* Callers must ensure that any changes to the callbackParameters object
* within a load() callback still permit a proper re-entry into the
* load() callback if an edit conflict is detected upon calling save().
*/
this.setCallbackParameters = function(callbackParameters) {
ctx.callbackParameters = callbackParameters;
};
/**
* @returns the object previous set by setCallbackParameters()
*/
this.getCallbackParameters = function() {
return ctx.callbackParameters;
};
/**
* @returns {Morebits.status} Status element created by the constructor
*/
this.getStatusElement = function() {
return ctx.statusElement;
};
/**
* @param {string} level The right required for edits not to require
* review. Possible options: none, autoconfirmed, review (not on enWiki).
* @param {string} expiry
*/
this.setFlaggedRevs = function(level, expiry) {
ctx.flaggedRevs = { level: level, expiry: expiry };
};
/**
* @returns {boolean} true if the page existed on the wiki when it was last loaded
*/
this.exists = function() {
return ctx.pageExists;
};
/**
* @returns {string} Page ID of the page loaded. 0 if the page doesn't
* exist.
*/
this.getPageID = function() {
return ctx.pageID;
};
/**
* @returns {string} ISO 8601 timestamp at which the page was last loaded
*/
this.getLoadTime = function() {
return ctx.loadTime;
};
/**
* @returns {string} the user who created the page following lookupCreation()
*/
this.getCreator = function() {
return ctx.creator;
};
/**
* @returns {string} the ISOString timestamp of page creation following lookupCreation()
*/
this.getCreationTimestamp = function() {
return ctx.timestamp;
};
/**
* Retrieves the username of the user who created the page as well as
* the timestamp of creation
* @param {Function} onSuccess - callback function (required) which is
* called when the username and timestamp are found within the callback.
* The username can be retrieved using the getCreator() function;
* the timestamp can be retrieved using the getCreationTimestamp() function
* Prior to June 2019 known as lookupCreator
*/
this.lookupCreation = function(onSuccess) {
if (!onSuccess) {
ctx.statusElement.error('Internal error: no onSuccess callback provided to lookupCreation()!');
return;
}
ctx.onLookupCreationSuccess = onSuccess;
var query = {
'action': 'query',
'prop': 'revisions',
'titles': ctx.pageName,
'rvlimit': 1,
'rvprop': 'user|timestamp',
'rvdir': 'newer'
};
// Only the wikitext content model can reliably handle
// rvsection, others return an error when paired with the
// content rvprop. Relatedly, non-wikitext models don't
// understand the #REDIRECT concept, so we shouldn't attempt
// the redirect resolution in fnLookupCreationSuccess
if (ctx.lookupNonRedirectCreator) {
query.rvsection = 0;
query.rvprop += '|content';
}
if (ctx.followRedirect) {
query.redirects = ''; // follow all redirects
}
ctx.lookupCreationApi = new Morebits.wiki.api('Retrieving page creation information', query, fnLookupCreationSuccess, ctx.statusElement);
ctx.lookupCreationApi.setParent(this);
ctx.lookupCreationApi.post();
};
/**
* Reverts a page to revertOldID
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.revert = function(onSuccess, onFailure) {
ctx.onSaveSuccess = onSuccess;
ctx.onSaveFailure = onFailure || emptyFunction;
if (!ctx.revertOldID) {
ctx.statusElement.error('Internal error: revision ID to revert to was not set before revert!');
ctx.onSaveFailure(this);
return;
}
ctx.editMode = 'revert';
this.load(fnAutoSave, ctx.onSaveFailure);
};
/**
* Moves a page to another title
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.move = function(onSuccess, onFailure) {
ctx.onMoveSuccess = onSuccess;
ctx.onMoveFailure = onFailure || emptyFunction;
if (!ctx.editSummary) {
ctx.statusElement.error('Internal error: move reason not set before move (use setEditSummary function)!');
ctx.onMoveFailure(this);
return;
}
if (!ctx.moveDestination) {
ctx.statusElement.error('Internal error: destination page name was not set before move!');
ctx.onMoveFailure(this);
return;
}
if (fnCanUseMwUserToken('move')) {
fnProcessMove.call(this, this);
} else {
var query = fnNeedTokenInfoQuery('move');
ctx.moveApi = new Morebits.wiki.api('retrieving token...', query, fnProcessMove, ctx.statusElement, ctx.onMoveFailure);
ctx.moveApi.setParent(this);
ctx.moveApi.post();
}
};
/**
* Marks the page as patrolled, using rcid (if available) or revid
*
* Patrolling as such doesn't need to rely on loading the page in
* question; simply passing a revid to the API is sufficient, so in
* those cases just using Morebits.wiki.api is probably preferable.
*
* No error handling since we don't actually care about the errors
*/
this.patrol = function() {
if (!Morebits.userIsSysop && !Morebits.userIsInGroup('patroller')) {
return;
}
// If a link is present, don't need to check if it's patrolled
if ($('.patrollink').length) {
var patrolhref = $('.patrollink a').attr('href');
ctx.rcid = mw.util.getParamValue('rcid', patrolhref);
fnProcessPatrol(this, this);
} else {
var patrolQuery = {
action: 'query',
prop: 'info',
meta: 'tokens',
type: 'patrol', // as long as we're querying, might as well get a token
list: 'recentchanges', // check if the page is unpatrolled
titles: ctx.pageName,
rcprop: 'patrolled',
rctitle: ctx.pageName,
rclimit: 1
};
ctx.patrolApi = new Morebits.wiki.api('retrieving token...', patrolQuery, fnProcessPatrol);
ctx.patrolApi.setParent(this);
ctx.patrolApi.post();
}
};
/**
* Marks the page as reviewed by the PageTriage extension
* https://www.mediawiki.org/wiki/Extension:PageTriage
*
* Referred to as "review" on-wiki
*
* Will, by it's nature, mark as patrolled as well. Falls back to
* patrolling if not in an appropriate namespace.
*
* Doesn't inherently rely on loading the page in question; simply
* passing a pageid to the API is sufficient, so in those cases just
* using Morebits.wiki.api is probably preferable.
*
* No error handling since we don't actually care about the errors
*/
this.triage = function() {
if (mw.config.get('pageTriageNamespaces') === null ) {
return; // localized [bypassed]
}
// Fall back to patrol if not a valid triage namespace
if (mw.config.get('pageTriageNamespaces').indexOf(mw.config.get('wgNamespaceNumber')) === -1) {
this.patrol();
} else {
if (!Morebits.userIsSysop && !Morebits.userIsInGroup('patroller')) {
return;
}
// If on the page in question, don't need to query for page ID
if (new mw.Title(Morebits.pageNameNorm).getPrefixedText() === new mw.Title(ctx.pageName).getPrefixedText()) {
ctx.pageID = mw.config.get('wgArticleId');
fnProcessTriage(this, this);
} else {
var query = fnNeedTokenInfoQuery('triage');
ctx.triageApi = new Morebits.wiki.api('retrieving token...', query, fnProcessTriage);
ctx.triageApi.setParent(this);
ctx.triageApi.post();
}
}
};
// |delete| is a reserved word in some flavours of JS
/**
* Deletes a page (for admins only)
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.deletePage = function(onSuccess, onFailure) {
ctx.onDeleteSuccess = onSuccess;
ctx.onDeleteFailure = onFailure || emptyFunction;
// if a non-admin tries to do this, don't bother
if (!Morebits.userIsSysop) {
ctx.statusElement.error('Cannot delete page: only admins can do that');
ctx.onDeleteFailure(this);
return;
}
if (!ctx.editSummary) {
ctx.statusElement.error('Internal error: delete reason not set before delete (use setEditSummary function)!');
ctx.onDeleteFailure(this);
return;
}
if (fnCanUseMwUserToken('delete')) {
fnProcessDelete.call(this, this);
} else {
var query = fnNeedTokenInfoQuery('delete');
ctx.deleteApi = new Morebits.wiki.api('retrieving token...', query, fnProcessDelete, ctx.statusElement, ctx.onDeleteFailure);
ctx.deleteApi.setParent(this);
ctx.deleteApi.post();
}
};
/**
* Undeletes a page (for admins only)
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.undeletePage = function(onSuccess, onFailure) {
ctx.onUndeleteSuccess = onSuccess;
ctx.onUndeleteFailure = onFailure || emptyFunction;
// if a non-admin tries to do this, don't bother
if (!Morebits.userIsSysop) {
ctx.statusElement.error('Cannot undelete page: only admins can do that');
ctx.onUndeleteFailure(this);
return;
}
if (!ctx.editSummary) {
ctx.statusElement.error('Internal error: undelete reason not set before undelete (use setEditSummary function)!');
ctx.onUndeleteFailure(this);
return;
}
if (fnCanUseMwUserToken('undelete')) {
fnProcessUndelete.call(this, this);
} else {
var query = fnNeedTokenInfoQuery('undelete');
ctx.undeleteApi = new Morebits.wiki.api('retrieving token...', query, fnProcessUndelete, ctx.statusElement, ctx.onUndeleteFailure);
ctx.undeleteApi.setParent(this);
ctx.undeleteApi.post();
}
};
/**
* Protects a page (for admins only)
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.protect = function(onSuccess, onFailure) {
ctx.onProtectSuccess = onSuccess;
ctx.onProtectFailure = onFailure || emptyFunction;
// if a non-admin tries to do this, don't bother
if (!Morebits.userIsSysop) {
ctx.statusElement.error('Cannot protect page: only admins can do that');
ctx.onProtectFailure(this);
return;
}
if (!ctx.protectEdit && !ctx.protectMove && !ctx.protectCreate) {
ctx.statusElement.error('Internal error: you must set edit and/or move and/or create protection before calling protect()!');
ctx.onProtectFailure(this);
return;
}
if (!ctx.editSummary) {
ctx.statusElement.error('Internal error: protection reason not set before protect (use setEditSummary function)!');
ctx.onProtectFailure(this);
return;
}
// because of the way MW API interprets protection levels
// (absolute, not differential), we always need to request
// protection levels from the server
var query = fnNeedTokenInfoQuery('protect');
ctx.protectApi = new Morebits.wiki.api('retrieving token...', query, fnProcessProtect, ctx.statusElement, ctx.onProtectFailure);
ctx.protectApi.setParent(this);
ctx.protectApi.post();
};
/**
* Apply FlaggedRevs protection-style settings
* only works where $wgFlaggedRevsProtection = true (i.e. where FlaggedRevs
* settings appear on the wiki's "protect" tab)
* @param {function} [onSuccess]
* @param {function} [onFailure]
*/
this.stabilize = function(onSuccess, onFailure) {
ctx.onStabilizeSuccess = onSuccess;
ctx.onStabilizeFailure = onFailure || emptyFunction;
// if a non-admin tries to do this, don't bother
if (!Morebits.userIsSysop) {
ctx.statusElement.error('Cannot apply FlaggedRevs settings: only admins can do that');
ctx.onStabilizeFailure(this);
return;
}
if (!ctx.flaggedRevs) {
ctx.statusElement.error('Internal error: you must set flaggedRevs before calling stabilize()!');
ctx.onStabilizeFailure(this);
return;
}
if (!ctx.editSummary) {
ctx.statusElement.error('Internal error: reason not set before calling stabilize() (use setEditSummary function)!');
ctx.onStabilizeFailure(this);
return;
}
if (fnCanUseMwUserToken('stabilize')) {
fnProcessStabilize.call(this, this);
} else {
var query = fnNeedTokenInfoQuery('stabilize');
ctx.stabilizeApi = new Morebits.wiki.api('retrieving token...', query, fnProcessStabilize, ctx.statusElement, ctx.onStabilizeFailure);
ctx.stabilizeApi.setParent(this);
ctx.stabilizeApi.post();
}
};
/*
* Private member functions
* These are not exposed outside
*/
/**
* Determines whether we can save an API call by using the csrf token sent with the page
* HTML, or whether we need to ask the server for more info (e.g. protection expiry).
*
* Only applicable for csrf token actions, e.g. not patrol
*
* Currently used for append, prepend, deletePage, undeletePage, move,
* and stabilize. Can't use for protect since it always needs to
* request protection status.
*
* @param {string} [action=edit] The action being undertaken, e.g.
* "edit" or "delete". In practice, only "edit" or "notedit" matters.
* @returns {boolean}
*/
var fnCanUseMwUserToken = function(action) {
action = typeof action !== 'undefined' ? action : 'edit'; // IE doesn't support default parameters
// API-based redirect resolution only works for action=query and
// action=edit in append/prepend modes (and section=new, but we don't
// really support that)
if (ctx.followRedirect && (action !== 'edit' ||
(ctx.editMode !== 'append' && ctx.editMode !== 'prepend'))) {
return false;
}
// do we need to fetch the edit protection expiry?
if (Morebits.userIsSysop && !ctx.suppressProtectWarning) {
if (new mw.Title(Morebits.pageNameNorm).getPrefixedText() === new mw.Title(ctx.pageName).getPrefixedText()) {
return false;
}
// wgRestrictionEdit is null on non-existent pages,
// so this neatly handles nonexistent pages
var editRestriction = mw.config.get('wgRestrictionEdit');
if (!editRestriction || editRestriction.indexOf('sysop') !== -1) {
return false;
}
}
return !!mw.user.tokens.get('csrfToken');
};
/**
* When functions can't use fnCanUseMwUserToken or require checking
* protection, maintain the query in one place. Used for delete,
* undelete, protect, stabilize, and move (basically, just not load)
*
* @param {string} action The action being undertaken, e.g. "edit" or
* "delete"
*/
var fnNeedTokenInfoQuery = function(action) {
var query = {
action: 'query',
meta: 'tokens',
type: 'csrf',
titles: ctx.pageName
};
// Protection not checked for flagged-revs or non-sysop moves
if (action !== 'stabilize' && (action !== 'move' || Morebits.userIsSysop)) {
query.prop = 'info';
query.inprop = 'protection';
}
if (ctx.followRedirect && action !== 'undelete') {
query.redirects = ''; // follow all redirects
}
return query;
};
// callback from loadSuccess() for append() and prepend() threads
var fnAutoSave = function(pageobj) {
pageobj.save(ctx.onSaveSuccess, ctx.onSaveFailure);
};
// callback from loadApi.post()
var fnLoadSuccess = function() {
var xml = ctx.loadApi.getXML();
if (!fnCheckPageName(xml, ctx.onLoadFailure)) {
return; // abort
}
ctx.pageExists = $(xml).find('page').attr('missing') !== '';
if (ctx.pageExists) {
ctx.pageText = $(xml).find('rev').text();
ctx.pageID = $(xml).find('page').attr('pageid');
} else {
ctx.pageText = ''; // allow for concatenation, etc.
ctx.pageID = 0; // nonexistent in response, matches wgArticleId
}
ctx.csrfToken = $(xml).find('tokens').attr('csrftoken');
if (!ctx.csrfToken) {
ctx.statusElement.error('Failed to retrieve edit token.');
ctx.onLoadFailure(this);
return;
}
ctx.loadTime = $(xml).find('api').attr('curtimestamp');
if (!ctx.loadTime) {
ctx.statusElement.error('Failed to retrieve current timestamp.');
ctx.onLoadFailure(this);
return;
}
// extract protection info, to alert admins when they are about to edit a protected page
if (Morebits.userIsSysop) {
var editprot = $(xml).find('pr[type="edit"]');
if (editprot.length > 0 && editprot.attr('level') === 'sysop') {
ctx.fullyProtected = editprot.attr('expiry');
} else {
ctx.fullyProtected = false;
}
}
ctx.lastEditTime = $(xml).find('rev').attr('timestamp');
ctx.revertCurID = $(xml).find('page').attr('lastrevid');
if (ctx.editMode === 'revert') {
ctx.revertCurID = $(xml).find('rev').attr('revid');
if (!ctx.revertCurID) {
ctx.statusElement.error('Failed to retrieve current revision ID.');
ctx.onLoadFailure(this);
return;
}
ctx.revertUser = $(xml).find('rev').attr('user');
if (!ctx.revertUser) {
if ($(xml).find('rev').attr('userhidden') === '') { // username was RevDel'd or oversighted
ctx.revertUser = '<username hidden>';
} else {
ctx.statusElement.error('Failed to retrieve user who made the revision.');
ctx.onLoadFailure(this);
return;
}
}
// set revert edit summary
ctx.editSummary = '[[Help:Revert|Reverted]] to revision ' + ctx.revertOldID + ' by ' + ctx.revertUser + ': ' + ctx.editSummary;
}
ctx.pageLoaded = true;
// alert("Generate edit conflict now"); // for testing edit conflict recovery logic
ctx.onLoadSuccess(this); // invoke callback
};
// helper function to parse the page name returned from the API
var fnCheckPageName = function(xml, onFailure) {
if (!onFailure) {
onFailure = emptyFunction;
}
// check for invalid titles
if ($(xml).find('page').attr('invalid') === '') {
ctx.statusElement.error('The page title is invalid: ' + ctx.pageName);
onFailure(this);
return false; // abort
}
// retrieve actual title of the page after normalization and redirects
if ($(xml).find('page').attr('title')) {
var resolvedName = $(xml).find('page').attr('title');
// only notify user for redirects, not normalization
if ($(xml).find('redirects').length > 0) {
Morebits.status.info('Info', 'Redirected from ' + ctx.pageName + ' to ' + resolvedName);
}
ctx.pageName = resolvedName; // always update in case of normalization
} else {
// could be a circular redirect or other problem
ctx.statusElement.error('Could not resolve redirects for: ' + ctx.pageName);
onFailure(this);
// force error to stay on the screen
++Morebits.wiki.numberOfActionsLeft;
return false; // abort
}
return true; // all OK
};
// helper function to get a new token on encountering token errors in save, deletePage, and undeletePage
var fnGetToken = function() {
var tokenApi = new Morebits.wiki.api('Getting token', {
action: 'query',
meta: 'tokens'
});
return tokenApi.post().then(function(apiobj) {
return $(apiobj.responseXML).find('tokens').attr('csrftoken');
});
};
// callback from saveApi.post()
var fnSaveSuccess = function() {
ctx.editMode = 'all'; // cancel append/prepend/revert modes
var xml = ctx.saveApi.getXML();
// see if the API thinks we were successful
if ($(xml).find('edit').attr('result') === 'Success') {
// real success
// default on success action - display link for edited page
var link = document.createElement('a');
link.setAttribute('href', mw.util.getUrl(ctx.pageName));
link.appendChild(document.createTextNode(ctx.pageName));
ctx.statusElement.info(['کامل شد (', link, ')']); // localized
if (ctx.onSaveSuccess) {
ctx.onSaveSuccess(this); // invoke callback
}
return;
}
// errors here are only generated by extensions which hook APIEditBeforeSave within MediaWiki,
// which as of 1.34.0-wmf.23 (Sept 2019) should only encompass captcha messages
if ($(xml).find('captcha').length > 0) {
ctx.statusElement.error('Could not save the page because the wiki server wanted you to fill out a CAPTCHA.');
} else {
ctx.statusElement.error('Unknown error received from API while saving page');
}
// force error to stay on the screen
++Morebits.wiki.numberOfActionsLeft;
ctx.onSaveFailure(this);
};
// callback from saveApi.post()
var fnSaveError = function() {
var errorCode = ctx.saveApi.getErrorCode();
// check for edit conflict
if (errorCode === 'editconflict' && ctx.conflictRetries++ < ctx.maxConflictRetries) {
// edit conflicts can occur when the page needs to be purged from the server cache
var purgeQuery = {
action: 'purge',
titles: ctx.pageName // redirects are already resolved
};
var purgeApi = new Morebits.wiki.api('Edit conflict detected, purging server cache', purgeQuery, function() {
--Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds
ctx.statusElement.info('Edit conflict detected, reapplying edit');
if (fnCanUseMwUserToken('edit')) {
ctx.saveApi.post(); // necessarily append or prepend, so this should work as desired
} else {
ctx.loadApi.post(); // reload the page and reapply the edit
}
}, ctx.statusElement);
purgeApi.post();
// check for loss of edit token
} else if (errorCode === 'badtoken' && ctx.retries++ < ctx.maxRetries) {
ctx.statusElement.info('Edit token is invalid, retrying');
--Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds
fnGetToken().then(function(token) {
ctx.saveApi.query.token = token;
ctx.saveApi.post();
});
// check for network or server error
} else if (errorCode === 'undefined' && ctx.retries++ < ctx.maxRetries) {
// the error might be transient, so try again
ctx.statusElement.info('Save failed, retrying');
--Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds
ctx.saveApi.post(); // give it another go!
// hard error, give up
} else {
// non-admin attempting to edit a protected page - this gives a friendlier message than the default
if (errorCode === 'protectedpage') {
ctx.statusElement.error('Failed to save edit: Page is protected');
// check for absuefilter hits: disallowed or warning
} else if (errorCode.indexOf('abusefilter') === 0) {
var desc = $(ctx.saveApi.getXML()).find('abusefilter').attr('description');
if (errorCode === 'abusefilter-disallowed') {
ctx.statusElement.error('The edit was disallowed by the edit filter: "' + desc + '".');
} else if (errorCode === 'abusefilter-warning') {
ctx.statusElement.error([ 'A warning was returned by the edit filter: "', desc, '". If you wish to proceed with the edit, please carry it out again. This warning will not appear a second time.' ]);
// We should provide the user with a way to automatically retry the action if they so choose -
// I can't see how to do this without creating a UI dependency on Morebits.wiki.page though -- TTO
} else { // shouldn't happen but...
ctx.statusElement.error('The edit was disallowed by the edit filter.');
}
// check for blacklist hits
} else if (errorCode === 'spamblacklist') {
// .find('matches') returns an array in case multiple items are blacklisted, we only return the first
var spam = $(ctx.saveApi.getXML()).find('spamblacklist').find('matches').children()[0].textContent;
ctx.statusElement.error('Could not save the page because the URL ' + spam + ' is on the spam blacklist');
} else {
ctx.statusElement.error('Failed to save edit: ' + ctx.saveApi.getErrorText());
}
ctx.editMode = 'all'; // cancel append/prepend/revert modes
if (ctx.onSaveFailure) {
ctx.onSaveFailure(this); // invoke callback
}
}
};
var fnLookupCreationSuccess = function() {
var xml = ctx.lookupCreationApi.getXML();
if (!fnCheckPageName(xml)) {
return; // abort
}
if (!ctx.lookupNonRedirectCreator || !/^\s*#redirect/i.test($(xml).find('rev').text())) {
ctx.creator = $(xml).find('rev').attr('user');
if (!ctx.creator) {
ctx.statusElement.error('Could not find name of page creator');
return;
}
ctx.timestamp = $(xml).find('rev').attr('timestamp');
if (!ctx.timestamp) {
ctx.statusElement.error('Could not find timestamp of page creation');
return;
}
ctx.onLookupCreationSuccess(this);
} else {
ctx.lookupCreationApi.query.rvlimit = 50; // modify previous query to fetch more revisions
ctx.lookupCreationApi.query.titles = ctx.pageName; // update pageName if redirect resolution took place in earlier query
ctx.lookupCreationApi = new Morebits.wiki.api('دریافت اطلاعات ساخت صفحه', ctx.lookupCreationApi.query, fnLookupNonRedirectCreator, ctx.statusElement); // localized
ctx.lookupCreationApi.setParent(this);
ctx.lookupCreationApi.post();
}
};
var fnLookupNonRedirectCreator = function() {
var xml = ctx.lookupCreationApi.getXML();
$(xml).find('rev').each(function(_, rev) {
if (!/^\s*#redirect/i.test(rev.textContent)) { // inaccessible revisions also check out
ctx.creator = rev.getAttribute('user');
ctx.timestamp = rev.getAttribute('timestamp');
return false; // break
}
});
if (!ctx.creator) {
// fallback to give first revision author if no non-redirect version in the first 50
ctx.creator = $(xml).find('rev')[0].getAttribute('user');
ctx.timestamp = $(xml).find('rev')[0].getAttribute('timestamp');
if (!ctx.creator) {
ctx.statusElement.error('Could not find name of page creator');
return;
}
}
if (!ctx.timestamp) {
ctx.statusElement.error('Could not find timestamp of page creation');
return;
}
ctx.onLookupCreationSuccess(this);
};
var fnProcessMove = function() {
var pageTitle, token;
if (fnCanUseMwUserToken('move')) {
token = mw.user.tokens.get('csrfToken');
pageTitle = ctx.pageName;
} else {
var xml = ctx.moveApi.getXML();
if ($(xml).find('page').attr('missing') === '') {
ctx.statusElement.error('Cannot move the page, because it no longer exists');
ctx.onMoveFailure(this);
return;
}
// extract protection info
if (Morebits.userIsSysop) {
var editprot = $(xml).find('pr[type="edit"]');
if (editprot.length > 0 && editprot.attr('level') === 'sysop' && !ctx.suppressProtectWarning &&
!confirm('You are about to move the fully protected page "' + ctx.pageName +
(editprot.attr('expiry') === 'infinity' ? '" (protected indefinitely)' : '" (protection expiring ' + new Morebits.date(editprot.attr('expiry')).calendar('utc') + ' (UTC))') +
'. \n\nClick OK to proceed with the move, or Cancel to skip this move.')) {
ctx.statusElement.error('Move of fully protected page was aborted.');
ctx.onMoveFailure(this);
return;
}
}
token = $(xml).find('tokens').attr('csrftoken');
if (!token) {
ctx.statusElement.error('Failed to retrieve move token.');
ctx.onMoveFailure(this);
return;
}
pageTitle = $(xml).find('page').attr('title');
}
var query = {
'action': 'move',
'from': pageTitle,
'to': ctx.moveDestination,
'token': token,
'reason': ctx.editSummary,
'watchlist': ctx.watchlistOption
};
if (ctx.moveTalkPage) {
query.movetalk = 'true';
}
if (ctx.moveSubpages) {
query.movesubpages = 'true';
}
if (ctx.moveSuppressRedirect) {
query.noredirect = 'true';
}
ctx.moveProcessApi = new Morebits.wiki.api('moving page...', query, ctx.onMoveSuccess, ctx.statusElement, ctx.onMoveFailure);
ctx.moveProcessApi.setParent(this);
ctx.moveProcessApi.post();
};
var fnProcessPatrol = function() {
var query = {
action: 'patrol'
};
// Didn't need to load the page
if (ctx.rcid) {
query.rcid = ctx.rcid;
query.token = mw.user.tokens.get('patrolToken');
} else {
var xml = ctx.patrolApi.getResponse();
// Don't patrol if not unpatrolled
if ($(xml).find('rc').attr('unpatrolled') !== '') {
return;
}
var lastrevid = $(xml).find('page').attr('lastrevid');
if (!lastrevid) {
return;
}
query.revid = lastrevid;
var token = $(xml).find('tokens').attr('patroltoken');
if (!token) {
return;
}
query.token = token;
}
var patrolStat = new Morebits.status('Marking page as patrolled');
ctx.patrolProcessApi = new Morebits.wiki.api('patrolling page...', query, null, patrolStat);
ctx.patrolProcessApi.setParent(this);
ctx.patrolProcessApi.post();
};
var fnProcessTriage = function() {
var pageID, token;
if (ctx.pageID) {
token = mw.user.tokens.get('csrfToken');
pageID = ctx.pageID;
} else {
var xml = ctx.triageApi.getXML();
pageID = $(xml).find('page').attr('pageid');
if (!pageID) {
return;
}
token = $(xml).find('tokens').attr('csrftoken');
if (!token) {
return;
}
}
var query = {
action: 'pagetriageaction',
pageid: pageID,
reviewed: 1,
token: token
};
var triageStat = new Morebits.status('Marking page as curated');
ctx.triageProcessApi = new Morebits.wiki.api('curating page...', query, null, triageStat, fnProcessTriageError);
ctx.triageProcessApi.setParent(this);
ctx.triageProcessApi.post();
};
// callback from triageProcessApi.post()
var fnProcessTriageError = function() {
// Ignore error if page not in queue, see https://github.com/azatoth/twinkle/pull/930
if (ctx.triageProcessApi.getErrorCode() === 'bad-pagetriage-page') {
ctx.triageProcessApi.getStatusElement().unlink();
}
};
var fnProcessDelete = function() {
var pageTitle, token;
if (fnCanUseMwUserToken('delete')) {
token = mw.user.tokens.get('csrfToken');
pageTitle = ctx.pageName;
} else {
var xml = ctx.deleteApi.getXML();
if ($(xml).find('page').attr('missing') === '') {
ctx.statusElement.error('Cannot delete the page, because it no longer exists');
ctx.onDeleteFailure(this);
return;
}
// extract protection info
var editprot = $(xml).find('pr[type="edit"]');
if (editprot.length > 0 && editprot.attr('level') === 'sysop' && !ctx.suppressProtectWarning &&
!confirm('You are about to delete the fully protected page "' + ctx.pageName +
(editprot.attr('expiry') === 'infinity' ? '" (protected indefinitely)' : '" (protection expiring ' + new Morebits.date(editprot.attr('expiry')).calendar('utc') + ' (UTC))') +
'. \n\nClick OK to proceed with the deletion, or Cancel to skip this deletion.')) {
ctx.statusElement.error('Deletion of fully protected page was aborted.');
ctx.onDeleteFailure(this);
return;
}
token = $(xml).find('tokens').attr('csrftoken');
if (!token) {
ctx.statusElement.error('Failed to retrieve delete token.');
ctx.onDeleteFailure(this);
return;
}
pageTitle = $(xml).find('page').attr('title');
}
var query = {
'action': 'delete',
'title': pageTitle,
'token': token,
'reason': ctx.editSummary,
'watchlist': ctx.watchlistOption
};
ctx.deleteProcessApi = new Morebits.wiki.api('deleting page...', query, ctx.onDeleteSuccess, ctx.statusElement, fnProcessDeleteError);
ctx.deleteProcessApi.setParent(this);
ctx.deleteProcessApi.post();
};
// callback from deleteProcessApi.post()
var fnProcessDeleteError = function() {
var errorCode = ctx.deleteProcessApi.getErrorCode();
// check for "Database query error"
if (errorCode === 'internal_api_error_DBQueryError' && ctx.retries++ < ctx.maxRetries) {
ctx.statusElement.info('Database query error, retrying');
--Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds
ctx.deleteProcessApi.post(); // give it another go!
} else if (errorCode === 'badtoken' && ctx.retries++ < ctx.maxRetries) {
ctx.statusElement.info('Invalid token, retrying');
--Morebits.wiki.numberOfActionsLeft;
fnGetToken().then(function(token) {
ctx.deleteProcessApi.query.token = token;
ctx.deleteProcessApi.post();
});
} else if (errorCode === 'missingtitle') {
ctx.statusElement.error('Cannot delete the page, because it no longer exists');
if (ctx.onDeleteFailure) {
ctx.onDeleteFailure.call(this, ctx.deleteProcessApi); // invoke callback
}
// hard error, give up
} else {
ctx.statusElement.error('Failed to delete the page: ' + ctx.deleteProcessApi.getErrorText());
if (ctx.onDeleteFailure) {
ctx.onDeleteFailure.call(this, ctx.deleteProcessApi); // invoke callback
}
}
};
var fnProcessUndelete = function() {
var pageTitle, token;
if (fnCanUseMwUserToken('undelete')) {
token = mw.user.tokens.get('csrfToken');
pageTitle = ctx.pageName;
} else {
var xml = ctx.undeleteApi.getXML();
if ($(xml).find('page').attr('missing') !== '') {
ctx.statusElement.error('Cannot undelete the page, because it already exists');
ctx.onUndeleteFailure(this);
return;
}
// extract protection info
var editprot = $(xml).find('pr[type="create"]');
if (editprot.length > 0 && editprot.attr('level') === 'sysop' && !ctx.suppressProtectWarning &&
!confirm('You are about to undelete the fully create protected page "' + ctx.pageName +
(editprot.attr('expiry') === 'infinity' ? '" (protected indefinitely)' : '" (protection expiring ' + new Morebits.date(editprot.attr('expiry')).calendar('utc') + ' (UTC))') +
'. \n\nClick OK to proceed with the undeletion, or Cancel to skip this undeletion.')) {
ctx.statusElement.error('Undeletion of fully create protected page was aborted.');
ctx.onUndeleteFailure(this);
return;
}
token = $(xml).find('tokens').attr('csrftoken');
if (!token) {
ctx.statusElement.error('Failed to retrieve undelete token.');
ctx.onUndeleteFailure(this);
return;
}
pageTitle = $(xml).find('page').attr('title');
}
var query = {
'action': 'undelete',
'title': pageTitle,
'token': token,
'reason': ctx.editSummary,
'watchlist': ctx.watchlistOption
};
ctx.undeleteProcessApi = new Morebits.wiki.api('undeleting page...', query, ctx.onUndeleteSuccess, ctx.statusElement, fnProcessUndeleteError);
ctx.undeleteProcessApi.setParent(this);
ctx.undeleteProcessApi.post();
};
// callback from undeleteProcessApi.post()
var fnProcessUndeleteError = function() {
var errorCode = ctx.undeleteProcessApi.getErrorCode();
// check for "Database query error"
if (errorCode === 'internal_api_error_DBQueryError' && ctx.retries++ < ctx.maxRetries) {
ctx.statusElement.info('Database query error, retrying');
--Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds
ctx.undeleteProcessApi.post(); // give it another go!
} else if (errorCode === 'badtoken' && ctx.retries++ < ctx.maxRetries) {
ctx.statusElement.info('Invalid token, retrying');
--Morebits.wiki.numberOfActionsLeft;
fnGetToken().then(function(token) {
ctx.undeleteProcessApi.query.token = token;
ctx.undeleteProcessApi.post();
});
} else if (errorCode === 'cantundelete') {
ctx.statusElement.error('Cannot undelete the page, either because there are no revisions to undelete or because it has already been undeleted');
if (ctx.onUndeleteFailure) {
ctx.onUndeleteFailure.call(this, ctx.undeleteProcessApi); // invoke callback
}
// hard error, give up
} else {
ctx.statusElement.error('Failed to undelete the page: ' + ctx.undeleteProcessApi.getErrorText());
if (ctx.onUndeleteFailure) {
ctx.onUndeleteFailure.call(this, ctx.undeleteProcessApi); // invoke callback
}
}
};
var fnProcessProtect = function() {
var xml = ctx.protectApi.getXML();
var missing = $(xml).find('page').attr('missing') === '';
if ((ctx.protectEdit || ctx.protectMove) && missing) {
ctx.statusElement.error('Cannot protect the page, because it no longer exists');
ctx.onProtectFailure(this);
return;
}
if (ctx.protectCreate && !missing) {
ctx.statusElement.error('Cannot create protect the page, because it already exists');
ctx.onProtectFailure(this);
return;
}
// TODO cascading protection not possible on edit<sysop
var token = $(xml).find('tokens').attr('csrftoken');
if (!token) {
ctx.statusElement.error('Failed to retrieve protect token.');
ctx.onProtectFailure(this);
return;
}
var pageTitle = $(xml).find('page').attr('title');
// fetch existing protection levels
var prs = $(xml).find('pr');
var editprot = prs.filter('[type="edit"]');
var moveprot = prs.filter('[type="move"]');
var createprot = prs.filter('[type="create"]');
var protections = [], expirys = [];
// set edit protection level
if (ctx.protectEdit) {
protections.push('edit=' + ctx.protectEdit.level);
expirys.push(ctx.protectEdit.expiry);
} else if (editprot.length) {
protections.push('edit=' + editprot.attr('level'));
expirys.push(editprot.attr('expiry').replace('infinity', 'indefinite'));
}
if (ctx.protectMove) {
protections.push('move=' + ctx.protectMove.level);
expirys.push(ctx.protectMove.expiry);
} else if (moveprot.length) {
protections.push('move=' + moveprot.attr('level'));
expirys.push(moveprot.attr('expiry').replace('infinity', 'indefinite'));
}
if (ctx.protectCreate) {
protections.push('create=' + ctx.protectCreate.level);
expirys.push(ctx.protectCreate.expiry);
} else if (createprot.length) {
protections.push('create=' + createprot.attr('level'));
expirys.push(createprot.attr('expiry').replace('infinity', 'indefinite'));
}
var query = {
action: 'protect',
title: pageTitle,
token: token,
protections: protections.join('|'),
expiry: expirys.join('|'),
reason: ctx.editSummary,
watchlist: ctx.watchlistOption
};
if (ctx.protectCascade) {
query.cascade = 'true';
}
ctx.protectProcessApi = new Morebits.wiki.api('protecting page...', query, ctx.onProtectSuccess, ctx.statusElement, ctx.onProtectFailure);
ctx.protectProcessApi.setParent(this);
ctx.protectProcessApi.post();
};
var fnProcessStabilize = function() {
var pageTitle, token;
if (fnCanUseMwUserToken('stabilize')) {
token = mw.user.tokens.get('csrfToken');
pageTitle = ctx.pageName;
} else {
var xml = ctx.stabilizeApi.getXML();
var missing = $(xml).find('page').attr('missing') === '';
if (missing) {
ctx.statusElement.error('Cannot protect the page, because it no longer exists');
ctx.onStabilizeFailure(this);
return;
}
token = $(xml).find('tokens').attr('csrftoken');
if (!token) {
ctx.statusElement.error('Failed to retrieve stabilize token.');
ctx.onStabilizeFailure(this);
return;
}
pageTitle = $(xml).find('page').attr('title');
}
var query = {
action: 'stabilize',
title: pageTitle,
token: token,
protectlevel: ctx.flaggedRevs.level,
expiry: ctx.flaggedRevs.expiry,
reason: ctx.editSummary
};
// [[phab:T247915]]
if (ctx.watchlistOption === 'watch') {
query.watchlist = 'true';
}
ctx.stabilizeProcessApi = new Morebits.wiki.api('configuring stabilization settings...', query, ctx.onStabilizeSuccess, ctx.statusElement, ctx.onStabilizeFailure);
ctx.stabilizeProcessApi.setParent(this);
ctx.stabilizeProcessApi.post();
};
}; // end Morebits.wiki.page
/* Morebits.wiki.page TODO: (XXX)
* - Should we retry loads also?
* - Need to reset current action before the save?
* - Deal with action.completed stuff
* - Need to reset all parameters once done (e.g. edit summary, move destination, etc.)
*/
/**
* **************** Morebits.wiki.preview ****************
* Uses the API to parse a fragment of wikitext and render it as HTML.
*
* The suggested implementation pattern (in Morebits.simpleWindow + Morebits.quickForm situations) is to
* construct a Morebits.wiki.preview object after rendering a Morebits.quickForm, and bind the object
* to an arbitrary property of the form (e.g. |previewer|). For an example, see
* twinklewarn.js.
*/
/**
* @constructor
* @param {HTMLElement} previewbox - the element that will contain the rendered HTML,
* usually a <div> element
*/
Morebits.wiki.preview = function(previewbox) {
this.previewbox = previewbox;
$(previewbox).addClass('morebits-previewbox').hide();
/**
* Displays the preview box, and begins an asynchronous attempt
* to render the specified wikitext.
* @param {string} wikitext - wikitext to render; most things should work, including subst: and ~~~~
* @param {string} [pageTitle] - optional parameter for the page this should be rendered as being on, if omitted it is taken as the current page
*/
this.beginRender = function(wikitext, pageTitle) {
$(previewbox).show();
var statusspan = document.createElement('span');
previewbox.appendChild(statusspan);
Morebits.status.init(statusspan);
var query = {
action: 'parse',
prop: 'text',
pst: 'true', // PST = pre-save transform; this makes substitution work properly
text: wikitext,
title: pageTitle || mw.config.get('wgPageName')
};
var renderApi = new Morebits.wiki.api('loading...', query, fnRenderSuccess, new Morebits.status('Preview'));
renderApi.post();
};
var fnRenderSuccess = function(apiobj) {
var xml = apiobj.getXML();
var html = $(xml).find('text').text();
if (!html) {
apiobj.statelem.error('failed to retrieve preview, or template was blanked');
return;
}
previewbox.innerHTML = html;
$(previewbox).find('a').attr('target', '_blank'); // this makes links open in new tab
};
/** Hides the preview box and clears it. */
this.closePreview = function() {
$(previewbox).empty().hide();
};
};
/**
* **************** Morebits.wikitext ****************
* Wikitext manipulation
*/
Morebits.wikitext = {};
Morebits.wikitext.template = {
parse: function(text, start) {
var count = -1;
var level = -1;
var equals = -1;
var current = '';
var result = {
name: '',
parameters: {}
};
var key, value;
for (var i = start; i < text.length; ++i) {
var test3 = text.substr(i, 3);
if (test3 === '{{{') {
current += '{{{';
i += 2;
++level;
continue;
}
if (test3 === '}}}') {
current += '}}}';
i += 2;
--level;
continue;
}
var test2 = text.substr(i, 2);
if (test2 === '{{' || test2 === '[[') {
current += test2;
++i;
++level;
continue;
}
if (test2 === ']]') {
current += ']]';
++i;
--level;
continue;
}
if (test2 === '}}') {
current += test2;
++i;
--level;
if (level <= 0) {
if (count === -1) {
result.name = current.substring(2).trim();
++count;
} else {
if (equals !== -1) {
key = current.substring(0, equals).trim();
value = current.substring(equals).trim();
result.parameters[key] = value;
equals = -1;
} else {
result.parameters[count] = current;
++count;
}
}
break;
}
continue;
}
if (text.charAt(i) === '|' && level <= 0) {
if (count === -1) {
result.name = current.substring(2).trim();
++count;
} else {
if (equals !== -1) {
key = current.substring(0, equals).trim();
value = current.substring(equals + 1).trim();
result.parameters[key] = value;
equals = -1;
} else {
result.parameters[count] = current;
++count;
}
}
current = '';
} else if (equals === -1 && text.charAt(i) === '=' && level <= 0) {
equals = current.length;
current += text.charAt(i);
} else {
current += text.charAt(i);
}
}
return result;
}
};
/**
* @constructor
* @param {string} text
*/
Morebits.wikitext.page = function mediawikiPage(text) {
this.text = text;
};
Morebits.wikitext.page.prototype = {
text: '',
/**
* Removes links to `link_target` from the page text.
* @param {string} link_target
*/
removeLink: function(link_target) {
var first_char = link_target.substr(0, 1);
var link_re_string = '[' + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape(link_target.substr(1), true);
// Files and Categories become links with a leading colon, e.g. [[:File:Test.png]]
// Otherwise, allow for an optional leading colon, e.g. [[:User:Test]]
var special_ns_re = /^(?:[Ff]ile|[Ii]mage|[Cc]ategory|پرونده|تصویر|رده):/; // localized
var colon = special_ns_re.test(link_target) ? ':' : ':?';
var link_simple_re = new RegExp('\\[\\[' + colon + '(' + link_re_string + ')\\]\\]', 'g');
var link_named_re = new RegExp('\\[\\[' + colon + link_re_string + '\\|(.+?)\\]\\]', 'g');
this.text = this.text.replace(link_simple_re, '$1').replace(link_named_re, '$1');
},
/**
* Comments out images from page text. If used in a gallery, deletes the whole line.
* If used as a template argument (not necessarily with File: prefix), the template parameter is commented out.
* @param {string} image - Image name without File: prefix
* @param {string} reason - Reason to be included in comment, alongside the commented-out image
*/
commentOutImage: function(image, reason) {
var unbinder = new Morebits.unbinder(this.text);
unbinder.unbind('<!--', '-->');
reason = reason ? reason + ': ' : '';
var first_char = image.substr(0, 1);
var image_re_string = '[' + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape(image.substr(1), true);
// Check for normal image links, i.e. [[File:Foobar.png|...]]
// Will eat the whole link
var links_re = new RegExp('\\[\\[(?:[Ii]mage|[Ff]ile|تصویر|پرونده):\\s*' + image_re_string); // localized
var allLinks = Morebits.array.uniq(Morebits.string.splitWeightedByKeys(unbinder.content, '[[', ']]'));
for (var i = 0; i < allLinks.length; ++i) {
if (links_re.test(allLinks[i])) {
var replacement = '<!-- ' + reason + allLinks[i] + ' -->';
unbinder.content = unbinder.content.replace(allLinks[i], replacement, 'g');
}
}
// unbind the newly created comments
unbinder.unbind('<!--', '-->');
// Check for gallery images, i.e. instances that must start on a new line,
// eventually preceded with some space, and must include File: prefix
// Will eat the whole line.
var gallery_image_re = new RegExp('(^\\s*(?:[Ii]mage|[Ff]ile|تصویر|پرونده):\\s*' + image_re_string + '.*?$)', 'mg'); // localized
unbinder.content = unbinder.content.replace(gallery_image_re, '<!-- ' + reason + '$1 -->');
// unbind the newly created comments
unbinder.unbind('<!--', '-->');
// Check free image usages, for example as template arguments, might have the File: prefix excluded, but must be preceeded by an |
// Will only eat the image name and the preceeding bar and an eventual named parameter
var free_image_re = new RegExp('(\\|\\s*(?:[\\w\\s]+\\=)?\\s*(?:(?:[Ii]mage|[Ff]ile|تصویر|پرونده):\\s*)?' + image_re_string + ')', 'mg'); // localized
unbinder.content = unbinder.content.replace(free_image_re, '<!-- ' + reason + '$1 -->');
// Rebind the content now, we are done!
this.text = unbinder.rebind();
},
/**
* Converts first usage of [[File:`image`]] to [[File:`image`|`data`]]
* @param {string} image - Image name without File: prefix
* @param {string} data
*/
addToImageComment: function(image, data) {
var first_char = image.substr(0, 1);
var first_char_regex = RegExp.escape(first_char, true);
if (first_char.toUpperCase() !== first_char.toLowerCase()) {
first_char_regex = '[' + RegExp.escape(first_char.toUpperCase(), true) + RegExp.escape(first_char.toLowerCase(), true) + ']';
}
var image_re_string = '(?:[Ii]mage|[Ff]ile|تصویر|پرونده):\\s*' + first_char_regex + RegExp.escape(image.substr(1), true); // localized
var links_re = new RegExp('\\[\\[' + image_re_string);
var allLinks = Morebits.array.uniq(Morebits.string.splitWeightedByKeys(this.text, '[[', ']]'));
for (var i = 0; i < allLinks.length; ++i) {
if (links_re.test(allLinks[i])) {
var replacement = allLinks[i];
// just put it at the end?
replacement = replacement.replace(/\]\]$/, '|' + data + ']]');
this.text = this.text.replace(allLinks[i], replacement, 'g');
}
}
var gallery_re = new RegExp('^(\\s*' + image_re_string + '.*?)\\|?(.*?)$', 'mg');
var newtext = '$1|$2 ' + data;
this.text = this.text.replace(gallery_re, newtext);
},
/**
* Removes transclusions of template from page text
* @param {string} template - Page name whose transclusions are to be removed,
* include namespace prefix only if not in template namespace
*/
removeTemplate: function(template) {
var first_char = template.substr(0, 1);
var template_re_string = '(?:[Tt]emplate:)?\\s*[' + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape(template.substr(1), true);
var links_re = new RegExp('\\{\\{' + template_re_string);
var allTemplates = Morebits.array.uniq(Morebits.string.splitWeightedByKeys(this.text, '{{', '}}', [ '{{{', '}}}' ]));
for (var i = 0; i < allTemplates.length; ++i) {
if (links_re.test(allTemplates[i])) {
this.text = this.text.replace(allTemplates[i], '', 'g');
}
}
},
/** @returns {string} */
getText: function() {
return this.text;
}
};
/**
* *********** Morebits.userspaceLogger ************
* Handles logging actions to a userspace log, used in
* twinklespeedy and twinkleprod.
*/
Morebits.userspaceLogger = function(logPageName) {
if (!logPageName) {
throw new Error('no log page name specified');
}
this.initialText = '';
this.headerLevel = 3;
this.log = function(logText, summaryText) {
if (!logText) {
return;
}
var page = new Morebits.wiki.page('User:' + mw.config.get('wgUserName') + '/' + logPageName,
'افزودن مورد به سیاههٔ کاربر'); // make this '... to ' + logPageName ? // localized
return page.load(function(pageobj) {
// add blurb if log page doesn't exist or is blank
var text = pageobj.getPageText() || this.initialText;
// create monthly header if it doesn't exist already
var date = new Morebits.date(pageobj.getLoadTime());
if (!date.monthHeaderRegex().exec(text)) {
text += '\n\n' + date.monthHeader(this.headerLevel);
}
pageobj.setPageText(text + '\n' + logText);
pageobj.setEditSummary(summaryText);
pageobj.setCreateOption('recreate');
pageobj.save();
}.bind(this));
};
};
/**
* **************** Morebits.status ****************
*/
/**
* @constructor
* Morebits.status.init() must be called before any status object is created, otherwise
* those statuses won't be visible.
* @param {String} text - Text before the the colon `:`
* @param {String} stat - Text after the colon `:`
* @param {String} [type=status] - This parameter determines the font color of the status line,
* this can be 'status' (blue), 'info' (green), 'warn' (red), or 'error' (bold red)
* The default is 'status'
*/
Morebits.status = function Status(text, stat, type) {
this.textRaw = text;
this.text = this.codify(text);
this.type = type || 'status';
this.generate();
if (stat) {
this.update(stat, type);
}
};
/**
* Specify an area for status message elements to be added to
* @param {HTMLElement} root - usually a div element
*/
Morebits.status.init = function(root) {
if (!(root instanceof Element)) {
throw new Error('object not an instance of Element');
}
while (root.hasChildNodes()) {
root.removeChild(root.firstChild);
}
Morebits.status.root = root;
Morebits.status.errorEvent = null;
};
Morebits.status.root = null;
/** @param {Function} handler - function to execute on error */
Morebits.status.onError = function(handler) {
if (typeof handler === 'function') {
Morebits.status.errorEvent = handler;
} else {
throw 'Morebits.status.onError: handler is not a function';
}
};
Morebits.status.prototype = {
stat: null,
text: null,
textRaw: null,
type: 'status',
target: null,
node: null,
linked: false,
/** Add the status element node to the DOM */
link: function() {
if (!this.linked && Morebits.status.root) {
Morebits.status.root.appendChild(this.node);
this.linked = true;
}
},
/** Remove the status element node from the DOM */
unlink: function() {
if (this.linked) {
Morebits.status.root.removeChild(this.node);
this.linked = false;
}
},
/**
* Create a document fragment with the status text
* @param {(string|Element|Array)} obj
* @returns {DocumentFragment}
*/
codify: function(obj) {
if (!Array.isArray(obj)) {
obj = [ obj ];
}
var result;
result = document.createDocumentFragment();
for (var i = 0; i < obj.length; ++i) {
if (typeof obj[i] === 'string') {
result.appendChild(document.createTextNode(obj[i]));
} else if (obj[i] instanceof Element) {
result.appendChild(obj[i]);
} // Else cosmic radiation made something shit
}
return result;
},
/**
* Update the status
* @param {String} status - Part of status message after colon `:`
* @param {String} type - 'status' (blue), 'info' (green), 'warn' (red), or 'error' (bold red)
*/
update: function(status, type) {
this.stat = this.codify(status);
if (type) {
this.type = type;
if (type === 'error') {
// hack to force the page not to reload when an error is output - see also Morebits.status() above
Morebits.wiki.numberOfActionsLeft = 1000;
// call error callback
if (Morebits.status.errorEvent) {
Morebits.status.errorEvent();
}
// also log error messages in the browser console
console.error(this.textRaw + ': ' + status); // eslint-disable-line no-console
}
}
this.render();
},
/** Produce the html for first part of the status message */
generate: function() {
this.node = document.createElement('div');
this.node.appendChild(document.createElement('span')).appendChild(this.text);
this.node.appendChild(document.createElement('span')).appendChild(document.createTextNode(': '));
this.target = this.node.appendChild(document.createElement('span'));
this.target.appendChild(document.createTextNode('')); // dummy node
},
/** Complete the html, for the second part of the status message */
render: function() {
this.node.className = 'morebits_status_' + this.type;
while (this.target.hasChildNodes()) {
this.target.removeChild(this.target.firstChild);
}
this.target.appendChild(this.stat);
this.link();
},
status: function(status) {
this.update(status, 'status');
},
info: function(status) {
this.update(status, 'info');
},
warn: function(status) {
this.update(status, 'warn');
},
error: function(status) {
this.update(status, 'error');
}
};
Morebits.status.info = function(text, status) {
return new Morebits.status(text, status, 'info');
};
Morebits.status.warn = function(text, status) {
return new Morebits.status(text, status, 'warn');
};
Morebits.status.error = function(text, status) {
return new Morebits.status(text, status, 'error');
};
/**
* For the action complete message at the end, create a status line without
* a colon separator.
* @param {String} text
*/
Morebits.status.actionCompleted = function(text) {
var node = document.createElement('div');
node.appendChild(document.createElement('span')).appendChild(document.createTextNode(text));
node.className = 'morebits_status_info';
if (Morebits.status.root) {
Morebits.status.root.appendChild(node);
}
};
/**
* Display the user's rationale, comments, etc. back to them after a failure,
* so that they may re-use it
* @param {string} comments
* @param {string} message
*/
Morebits.status.printUserText = function(comments, message) {
var p = document.createElement('p');
p.innerHTML = message;
var div = document.createElement('div');
div.className = 'toccolours';
div.style.marginTop = '0';
div.style.whiteSpace = 'pre-wrap';
div.textContent = comments;
p.appendChild(div);
Morebits.status.root.appendChild(p);
};
/**
* **************** Morebits.htmlNode() ****************
* Simple helper function to create a simple node
* @param {string} type - type of HTML element
* @param {string} text - text content
* @param {string} [color] - font color
* @returns {HTMLElement}
*/
Morebits.htmlNode = function (type, content, color) {
var node = document.createElement(type);
if (color) {
node.style.color = color;
}
node.appendChild(document.createTextNode(content));
return node;
};
/**
* **************** Morebits.checkboxShiftClickSupport() ****************
* shift-click-support for checkboxes
* wikibits version (window.addCheckboxClickHandlers) has some restrictions, and
* doesn't work with checkboxes inside a sortable table, so let's build our own.
*/
Morebits.checkboxShiftClickSupport = function (jQuerySelector, jQueryContext) {
var lastCheckbox = null;
function clickHandler(event) {
var thisCb = this;
if (event.shiftKey && lastCheckbox !== null) {
var cbs = $(jQuerySelector, jQueryContext); // can't cache them, obviously, if we want to support resorting
var index = -1, lastIndex = -1, i;
for (i = 0; i < cbs.length; i++) {
if (cbs[i] === thisCb) {
index = i;
if (lastIndex > -1) {
break;
}
}
if (cbs[i] === lastCheckbox) {
lastIndex = i;
if (index > -1) {
break;
}
}
}
if (index > -1 && lastIndex > -1) {
// inspired by wikibits
var endState = thisCb.checked;
var start, finish;
if (index < lastIndex) {
start = index + 1;
finish = lastIndex;
} else {
start = lastIndex;
finish = index - 1;
}
for (i = start; i <= finish; i++) {
if (cbs[i].checked !== endState) {
cbs[i].click();
}
}
}
}
lastCheckbox = thisCb;
return true;
}
$(jQuerySelector, jQueryContext).click(clickHandler);
};
/** **************** Morebits.batchOperation ****************
* Iterates over a group of pages (or arbitrary objects) and executes a worker function
* for each.
*
* Constructor: Morebits.batchOperation(currentAction)
*
* setPageList(wikitext): Sets the list of pages to work on.
* It should be an array of page names (strings).
*
* setOption(optionName, optionValue): Sets a known option:
* - chunkSize (integer): the size of chunks to break the array into (default 50).
* Setting this to a small value (<5) can cause problems.
* - preserveIndividualStatusLines (boolean): keep each page's status element visible
* when worker is complete? See note below
*
* run(worker, postFinish): Runs the callback `worker` for each page in the list.
* The callback must call workerSuccess when succeeding, or workerFailure
* when failing. If using Morebits.wiki.api or Morebits.wiki.page, this is easily
* done by passing these two functions as parameters to the methods on those
* objects, for instance, page.save(batchOp.workerSuccess, batchOp.workerFailure).
* Make sure the methods are called directly if special success/failure cases arise.
* If you omit to call these methods, the batch operation will stall after the first
* chunk! Also ensure that either workerSuccess or workerFailure is called no more
* than once.
* The second callback `postFinish` is executed when the entire batch has been processed.
*
* If using preserveIndividualStatusLines, you should try to ensure that the
* workerSuccess callback has access to the page title. This is no problem for
* Morebits.wiki.page objects. But when using the API, please set the
* |pageName| property on the Morebits.wiki.api object.
*
* There are sample batchOperation implementations using Morebits.wiki.page in
* twinklebatchdelete.js, twinklebatchundelete.js, and twinklebatchprotect.js.
*/
/**
* @constructor
* @param {string} [currentAction]
*/
Morebits.batchOperation = function(currentAction) {
var ctx = {
// backing fields for public properties
pageList: null,
options: {
chunkSize: 50,
preserveIndividualStatusLines: false
},
// internal counters, etc.
statusElement: new Morebits.status(currentAction || 'Performing batch operation'),
worker: null, // function that executes for each item in pageList
postFinish: null, // function that executes when the whole batch has been processed
countStarted: 0,
countFinished: 0,
countFinishedSuccess: 0,
currentChunkIndex: -1,
pageChunks: [],
running: false
};
// shouldn't be needed by external users, but provided anyway for maximum flexibility
this.getStatusElement = function() {
return ctx.statusElement;
};
/**
* Sets the list of pages to work on
* @param {Array} pageList Array of objects over which you wish to execute the worker function
* This is usually the list of page names (strings).
*/
this.setPageList = function(pageList) {
ctx.pageList = pageList;
};
/**
* Sets a known option:
* - chunkSize (integer):
* The size of chunks to break the array into (default 50).
* Setting this to a small value (<5) can cause problems.
* - preserveIndividualStatusLines (boolean):
* Keep each page's status element visible when worker is complete?
*/
this.setOption = function(optionName, optionValue) {
ctx.options[optionName] = optionValue;
};
/**
* Runs the first callback for each page in the list.
* The callback must call workerSuccess when succeeding, or workerFailure when failing.
* Runs the second callback when the whole batch has been processed (optional)
* @param {Function} worker
* @param {Function} [postFinish]
*/
this.run = function(worker, postFinish) {
if (ctx.running) {
ctx.statusElement.error('Batch operation is already running');
return;
}
ctx.running = true;
ctx.worker = worker;
ctx.postFinish = postFinish;
ctx.countStarted = 0;
ctx.countFinished = 0;
ctx.countFinishedSuccess = 0;
ctx.currentChunkIndex = -1;
ctx.pageChunks = [];
var total = ctx.pageList.length;
if (!total) {
ctx.statusElement.info('no pages specified');
ctx.running = false;
if (ctx.postFinish) {
ctx.postFinish();
}
return;
}
// chunk page list into more manageable units
ctx.pageChunks = Morebits.array.chunk(ctx.pageList, ctx.options.chunkSize);
// start the process
Morebits.wiki.addCheckpoint();
ctx.statusElement.status('0%');
fnStartNewChunk();
};
/**
* To be called by worker before it terminates succesfully
* @param {(Morebits.wiki.page|Morebits.wiki.api|string)} arg
* This should be the `Morebits.wiki.page` or `Morebits.wiki.api` object used by worker
* (for the adjustment of status lines emitted by them).
* If no Morebits.wiki.* object is used (eg. you're using mw.Api() or something else), and
* `preserveIndividualStatusLines` option is on, give the page name (string) as argument.
*/
this.workerSuccess = function(arg) {
var createPageLink = function(pageName) {
var link = document.createElement('a');
link.setAttribute('href', mw.util.getUrl(pageName));
link.appendChild(document.createTextNode(pageName));
return link;
};
if (arg instanceof Morebits.wiki.api || arg instanceof Morebits.wiki.page) {
// update or remove status line
var statelem = arg.getStatusElement();
if (ctx.options.preserveIndividualStatusLines) {
if (arg.getPageName || arg.pageName || (arg.query && arg.query.title)) {
// we know the page title - display a relevant message
var pageName = arg.getPageName ? arg.getPageName() : arg.pageName || arg.query.title;
statelem.info(['completed (', createPageLink(pageName), ')']);
} else {
// we don't know the page title - just display a generic message
statelem.info('انجام شد'); // localized
}
} else {
// remove the status line automatically produced by Morebits.wiki.*
statelem.unlink();
}
} else if (typeof arg === 'string' && ctx.options.preserveIndividualStatusLines) {
new Morebits.status(arg, ['done (', createPageLink(arg), ')']);
}
ctx.countFinishedSuccess++;
fnDoneOne();
};
this.workerFailure = function() {
fnDoneOne();
};
// private functions
var thisProxy = this;
var fnStartNewChunk = function() {
var chunk = ctx.pageChunks[++ctx.currentChunkIndex];
if (!chunk) {
return; // done! yay
}
// start workers for the current chunk
ctx.countStarted += chunk.length;
chunk.forEach(function(page) {
ctx.worker(page, thisProxy);
});
};
var fnDoneOne = function() {
ctx.countFinished++;
// update overall status line
var total = ctx.pageList.length;
if (ctx.countFinished === total) {
var statusString = 'Done (' + ctx.countFinishedSuccess +
'/' + ctx.countFinished + ' actions completed successfully)';
if (ctx.countFinishedSuccess < ctx.countFinished) {
ctx.statusElement.warn(statusString);
} else {
ctx.statusElement.info(statusString);
}
if (ctx.postFinish) {
ctx.postFinish();
}
Morebits.wiki.removeCheckpoint();
ctx.running = false;
return;
}
// just for giggles! (well, serious debugging, actually)
if (ctx.countFinished > total) {
ctx.statusElement.warn('Done (overshot by ' + (ctx.countFinished - total) + ')');
Morebits.wiki.removeCheckpoint();
ctx.running = false;
return;
}
ctx.statusElement.status(parseInt(100 * ctx.countFinished / total, 10) + '%');
// start a new chunk if we're close enough to the end of the previous chunk, and
// we haven't already started the next one
if (ctx.countFinished >= (ctx.countStarted - Math.max(ctx.options.chunkSize / 10, 2)) &&
Math.floor(ctx.countFinished / ctx.options.chunkSize) > ctx.currentChunkIndex) {
fnStartNewChunk();
}
};
};
/**
* **************** Morebits.simpleWindow ****************
* A simple draggable window
* now a wrapper for jQuery UI's dialog feature
* @requires {jquery.ui.dialog}
*/
/**
* @constructor
* @param {number} width
* @param {number} height The maximum allowable height for the content area.
*/
Morebits.simpleWindow = function SimpleWindow(width, height) {
var content = document.createElement('div');
this.content = content;
content.className = 'morebits-dialog-content';
content.id = 'morebits-dialog-content-' + Math.round(Math.random() * 1e15);
this.height = height;
$(this.content).dialog({
autoOpen: false,
buttons: { 'Placeholder button': function() {} },
dialogClass: 'morebits-dialog',
width: Math.min(parseInt(window.innerWidth, 10), parseInt(width ? width : 800, 10)),
// give jQuery the given height value (which represents the anticipated height of the dialog) here, so
// it can position the dialog appropriately
// the 20 pixels represents adjustment for the extra height of the jQuery dialog "chrome", compared
// to that of the old SimpleWindow
height: height + 20,
close: function(event) {
// dialogs and their content can be destroyed once closed
$(event.target).dialog('destroy').remove();
},
resizeStart: function() {
this.scrollbox = $(this).find('.morebits-scrollbox')[0];
if (this.scrollbox) {
this.scrollbox.style.maxHeight = 'none';
}
},
resizeEnd: function() {
this.scrollbox = null;
},
resize: function() {
this.style.maxHeight = '';
if (this.scrollbox) {
this.scrollbox.style.width = '';
}
}
});
var $widget = $(this.content).dialog('widget');
// delete the placeholder button (it's only there so the buttonpane gets created)
$widget.find('button').each(function(key, value) {
value.parentNode.removeChild(value);
});
// add container for the buttons we add, and the footer links (if any)
var buttonspan = document.createElement('span');
buttonspan.className = 'morebits-dialog-buttons';
var linksspan = document.createElement('span');
linksspan.className = 'morebits-dialog-footerlinks';
$widget.find('.ui-dialog-buttonpane').append(buttonspan, linksspan);
// resize the scrollbox with the dialog, if one is present
$widget.resizable('option', 'alsoResize', '#' + this.content.id + ' .morebits-scrollbox, #' + this.content.id);
};
Morebits.simpleWindow.prototype = {
buttons: [],
height: 600,
hasFooterLinks: false,
scriptName: null,
/**
* Focuses the dialog. This might work, or on the contrary, it might not.
* @returns {Morebits.simpleWindow}
*/
focus: function() {
$(this.content).dialog('moveToTop');
return this;
},
/**
* Closes the dialog. If this is set as an event handler, it will stop the event
* from doing anything more.
* @returns {Morebits.simpleWindow}
*/
close: function(event) {
if (event) {
event.preventDefault();
}
$(this.content).dialog('close');
return this;
},
/**
* Shows the dialog. Calling display() on a dialog that has previously been closed
* might work, but it is not guaranteed.
* @returns {Morebits.simpleWindow}
*/
display: function() {
if (this.scriptName) {
var $widget = $(this.content).dialog('widget');
$widget.find('.morebits-dialog-scriptname').remove();
var scriptnamespan = document.createElement('span');
scriptnamespan.className = 'morebits-dialog-scriptname';
scriptnamespan.textContent = this.scriptName + ' \u00B7 '; // U+00B7 MIDDLE DOT = ·
$widget.find('.ui-dialog-title').prepend(scriptnamespan);
}
var dialog = $(this.content).dialog('open');
if (window.setupTooltips && window.pg && window.pg.re && window.pg.re.diff) { // tie in with NAVPOP
dialog.parent()[0].ranSetupTooltipsAlready = false;
window.setupTooltips(dialog.parent()[0]);
}
this.setHeight(this.height); // init height algorithm
return this;
},
/**
* Sets the dialog title.
* @param {string} title
* @returns {Morebits.simpleWindow}
*/
setTitle: function(title) {
$(this.content).dialog('option', 'title', title);
return this;
},
/**
* Sets the script name, appearing as a prefix to the title to help users determine which
* user script is producing which dialog. For instance, Twinkle modules set this to "Twinkle".
* @param {string} name
* @returns {Morebits.simpleWindow}
*/
setScriptName: function(name) {
this.scriptName = name;
return this;
},
/**
* Sets the dialog width.
* @param {number} width
* @returns {Morebits.simpleWindow}
*/
setWidth: function(width) {
$(this.content).dialog('option', 'width', width);
return this;
},
/**
* Sets the dialog's maximum height. The dialog will auto-size to fit its contents,
* but the content area will grow no larger than the height given here.
* @param {number} height
* @returns {Morebits.simpleWindow}
*/
setHeight: function(height) {
this.height = height;
// from display time onwards, let the browser determine the optimum height,
// and instead limit the height at the given value
// note that the given height will exclude the approx. 20px that the jQuery UI
// chrome has in height in addition to the height of an equivalent "classic"
// Morebits.simpleWindow
if (parseInt(getComputedStyle($(this.content).dialog('widget')[0], null).height, 10) > window.innerHeight) {
$(this.content).dialog('option', 'height', window.innerHeight - 2).dialog('option', 'position', 'top');
} else {
$(this.content).dialog('option', 'height', 'auto');
}
$(this.content).dialog('widget').find('.morebits-dialog-content')[0].style.maxHeight = parseInt(this.height - 30, 10) + 'px';
return this;
},
/**
* Sets the content of the dialog to the given element node, usually from rendering
* a Morebits.quickForm.
* Re-enumerates the footer buttons, but leaves the footer links as they are.
* Be sure to call this at least once before the dialog is displayed...
* @param {HTMLElement} content
* @returns {Morebits.simpleWindow}
*/
setContent: function(content) {
this.purgeContent();
this.addContent(content);
return this;
},
/**
* Adds the given element node to the dialog content.
* @param {HTMLElement} content
* @returns {Morebits.simpleWindow}
*/
addContent: function(content) {
this.content.appendChild(content);
// look for submit buttons in the content, hide them, and add a proxy button to the button pane
var thisproxy = this;
$(this.content).find('input[type="submit"], button[type="submit"]').each(function(key, value) {
value.style.display = 'none';
var button = document.createElement('button');
button.textContent = value.hasAttribute('value') ? value.getAttribute('value') : value.textContent ? value.textContent : 'ثبت درخواست'; // localized
button.className = value.className || 'submitButtonProxy';
// here is an instance of cheap coding, probably a memory-usage hit in using a closure here
button.addEventListener('click', function() {
value.click();
}, false);
thisproxy.buttons.push(button);
});
// remove all buttons from the button pane and re-add them
if (this.buttons.length > 0) {
$(this.content).dialog('widget').find('.morebits-dialog-buttons').empty().append(this.buttons)[0].removeAttribute('data-empty');
} else {
$(this.content).dialog('widget').find('.morebits-dialog-buttons')[0].setAttribute('data-empty', 'data-empty'); // used by CSS
}
return this;
},
/**
* Removes all contents from the dialog, barring any footer links
* @returns {Morebits.simpleWindow}
*/
purgeContent: function() {
this.buttons = [];
// delete all buttons in the buttonpane
$(this.content).dialog('widget').find('.morebits-dialog-buttons').empty();
while (this.content.hasChildNodes()) {
this.content.removeChild(this.content.firstChild);
}
return this;
},
/**
* Adds a link in the bottom-right corner of the dialog.
* This can be used to provide help or policy links.
* For example, Twinkle's CSD module adds a link to the CSD policy page,
* as well as a link to Twinkle's documentation.
* @param {string} text Link's text content
* @param {string} wikiPage Link target
* @param {boolean} [prep=false] Set true to prepend rather than append
* @returns {Morebits.simpleWindow}
*/
addFooterLink: function(text, wikiPage, prep) {
var $footerlinks = $(this.content).dialog('widget').find('.morebits-dialog-footerlinks');
if (this.hasFooterLinks) {
var bullet = document.createElement('span');
bullet.textContent = ' \u2022 '; // U+2022 BULLET
if (prep) {
$footerlinks.prepend(bullet);
} else {
$footerlinks.append(bullet);
}
}
var link = document.createElement('a');
link.setAttribute('href', mw.util.getUrl(wikiPage));
link.setAttribute('title', wikiPage);
link.setAttribute('target', '_blank');
link.textContent = text;
if (prep) {
$footerlinks.prepend(link);
} else {
$footerlinks.append(link);
}
this.hasFooterLinks = true;
return this;
},
/**
* Set whether the window should be modal or not.
* If set to true, other items on the page will be disabled, i.e., cannot be
* interacted with. Modal dialogs create an overlay below the dialog but above
* other page elements.
* This must be used (if necessary) before calling display()
* Default: false
* @param {boolean} modal
* @returns {Morebits.simpleWindow}
*/
setModality: function(modal) {
$(this.content).dialog('option', 'modal', modal);
return this;
}
};
/**
* Enables or disables all footer buttons on all Morebits.simpleWindows in the current page.
* This should be called with `false` when the button(s) become irrelevant (e.g. just before
* Morebits.status.init is called).
* This is not an instance method so that consumers don't have to keep a reference to the
* original Morebits.simpleWindow object sitting around somewhere. Anyway, most of the time
* there will only be one Morebits.simpleWindow open, so this shouldn't matter.
* @param {boolean} enabled
*/
Morebits.simpleWindow.setButtonsEnabled = function(enabled) {
$('.morebits-dialog-buttons button').prop('disabled', !enabled);
};
// Twinkle blacklist was removed per consensus at http://en.wikipedia.org/wiki/Wikipedia:Administrators%27_noticeboard/Archive221#New_Twinkle_blacklist_proposal
}(window, document, jQuery)); // End wrap with anonymous function
/**
* If this script is being executed outside a ResourceLoader context, we add some
* global assignments for legacy scripts, hopefully these can be removed down the line
*
* IMPORTANT NOTE:
* PLEASE DO NOT USE THESE ALIASES IN NEW CODE!
* Thanks.
*/
if (typeof arguments === 'undefined') { // typeof is here for a reason...
/* global Morebits */
window.SimpleWindow = Morebits.simpleWindow;
window.QuickForm = Morebits.quickForm;
window.Wikipedia = Morebits.wiki;
window.Status = Morebits.status;
}
// </nowiki>
/* ===== Twinkle.js ===== */
/**
* +-------------------------------------------------------------------------+
* | === WARNING: GLOBAL GADGET FILE === |
* | Changes to this page affect many users. |
* | Please discuss changes at [[WT:TW]] before editing. |
* +-------------------------------------------------------------------------+
*
* Imported from github [https://github.com/azatoth/twinkle].
* All changes should be made in the repository, otherwise they will be lost.
*
* ----------
*
* This is AzaToth's Twinkle, the popular script sidekick for newbies, admins, and
* every Wikipedian in between. Visit [[WP:TW]] for more information.
*/
// <nowiki>
/* global Morebits */
(function (window, document, $) { // Wrap with anonymous function
// Check if account is experienced enough to use Twinkle
if (!Morebits.userIsInGroup('autoconfirmed') && !Morebits.userIsInGroup('confirmed')) {
return;
}
var Twinkle = {};
window.Twinkle = Twinkle; // allow global access
// for use by custom modules (normally empty)
Twinkle.initCallbacks = [];
Twinkle.addInitCallback = function twinkleAddInitCallback(func) {
Twinkle.initCallbacks.push(func);
};
Twinkle.defaultConfig = {};
/**
* This holds the default set of preferences used by Twinkle.
* It is important that all new preferences added here, especially admin-only ones, are also added to
* |Twinkle.config.sections| in twinkleconfig.js, so they are configurable via the Twinkle preferences panel.
* For help on the actual preferences, see the comments in twinkleconfig.js.
*
* Formerly Twinkle.defaultConfig.twinkle and Twinkle.defaultConfig.friendly
*/
Twinkle.defaultConfig = {
// General
summaryAd: ' ([[ویکیواژه:توینکل|توینکل]])', // localized
deletionSummaryAd: ' ([[ویکیواژه:توینکل|توینکل]])', // localized
protectionSummaryAd: ' ([[ویکیواژه:توینکل|توینکل]])', // localized
userTalkPageMode: 'tab',
dialogLargeFont: false,
disabledModules: [],
disabledSysopModules: [],
// Block
defaultToPartialBlocks: false,
blankTalkpageOnIndefBlock: false,
// Fluff (revert and rollback)
autoMenuAfterRollback: false,
openTalkPage: [ 'agf', 'norm', 'vand' ],
openTalkPageOnAutoRevert: false,
rollbackInPlace: false,
markRevertedPagesAsMinor: [ 'vand' ],
watchRevertedPages: [ 'agf', 'norm', 'vand', 'torev' ],
offerReasonOnNormalRevert: true,
confirmOnFluff: false,
showRollbackLinks: [ 'diff', 'others' ],
// CSD
speedySelectionStyle: 'buttonClick',
watchSpeedyPages: [ "ع۳", "ع۵", "ع۱۰", "ع۱۱", "ع۱۲" ], // localized
markSpeedyPagesAsPatrolled: false,
// these next two should probably be identical by default
welcomeUserOnSpeedyDeletionNotification: [ "حذف سریع", "ع۱", "ع۲", "ع۳", "ع۴", "ع۶", "ع۱۰", "ع۱۱", "ع۱۲", "ع۱۳", "م۱", "م۲", "م۳", "م۵", "م۷", "م۹", "م۱۰", "م۱۱", "پ۱", "پ۲", "پ۳", "پ۷", "پ۹", "پ۱۰", "ک۳", "ک۵", "ت۲", "ت۳", "د۱", "د۲" ], // localized
notifyUserOnSpeedyDeletionNomination: [ "حذف سریع", "ع۱", "ع۲", "ع۳", "ع۴", "ع۶", "ع۱۰", "ع۱۱", "ع۱۲", "ع۱۳", "م۱", "م۲", "م۳", "م۵", "م۷", "م۹", "م۱۰", "م۱۱", "پ۱", "پ۲", "پ۳", "پ۷", "پ۹", "پ۱۰", "ک۳", "ک۵", "ت۲", "ت۳", "د۱", "د۲" ], // localized
warnUserOnSpeedyDelete: [ "حذف سریع", "ع۱", "ع۲", "ع۳", "ع۴", "ع۵", "ع۱۰", "ع۱۱", "ع۱۲", "م۱", "م۳", "م۷", "م۹", "م۱۰", "م۱۱", "پ۳", "پ۷", "پ۹", "ک۳", "ک۵", "ت۲", "د۱" ], // localized
promptForSpeedyDeletionSummary: [],
deleteTalkPageOnDelete: true,
deleteRedirectsOnDelete: true,
deleteSysopDefaultToDelete: false,
speedyWindowHeight: 500,
speedyWindowWidth: 800,
logSpeedyNominations: false,
speedyLogPageName: 'نامزدهای حذف سریع', // localized
noLogOnSpeedyNomination: [ 'ک۱' ], // localized
// Unlink
unlinkNamespaces: [ '0', '10', '100', '118' ],
// Warn
defaultWarningGroup: '1',
combinedSingletMenus: false,
showSharedIPNotice: true,
watchWarnings: true,
oldSelect: false,
customWarningList: [],
// Hidden preferences
revertMaxRevisions: 50,
autolevelStaleDays: 3, // Huggle is 3, CBNG is 2
batchMax: 5000,
batchdeleteChunks: 50,
batchProtectChunks: 50,
batchundeleteChunks: 50,
proddeleteChunks: 50,
// Formerly defaultConfig.friendly:
// Tag
groupByDefault: true,
watchTaggedPages: true,
watchMergeDiscussions: true,
markTaggedPagesAsMinor: false,
markTaggedPagesAsPatrolled: true,
tagArticleSortOrder: 'cat',
customTagList: [],
customFileTagList: [],
customRedirectTagList: [],
// Welcome
topWelcomes: false,
watchWelcomes: true,
welcomeHeading: 'Welcome',
insertHeadings: true,
insertUsername: true,
insertSignature: true, // sign welcome templates, where appropriate
quickWelcomeMode: 'norm',
quickWelcomeTemplate: 'welcome',
customWelcomeList: [],
customWelcomeSignature: true,
// Talkback
markTalkbackAsMinor: true,
insertTalkbackSignature: true, // always sign talkback templates
talkbackHeading: 'بازبحث ' + mw.config.get('wgUserName'), // localized
adminNoticeHeading: 'آگاهسازی', // localized
mailHeading: "نامه دارید!", // localized
// Shared
markSharedIPAsMinor: true
};
// now some skin dependent config.
switch (mw.config.get('skin')) {
case 'vector':
Twinkle.defaultConfig.portletArea = 'right-navigation';
Twinkle.defaultConfig.portletId = 'p-twinkle';
Twinkle.defaultConfig.portletName = 'توینکل'; // localized
Twinkle.defaultConfig.portletType = 'menu';
Twinkle.defaultConfig.portletNext = 'p-cactions'; // localized
break;
case 'timeless':
Twinkle.defaultConfig.portletArea = '#page-tools .sidebar-inner';
Twinkle.defaultConfig.portletId = 'p-twinkle';
Twinkle.defaultConfig.portletName = 'توینکل'; // localized
Twinkle.defaultConfig.portletType = null;
Twinkle.defaultConfig.portletNext = 'p-userpagetools';
break;
default:
Twinkle.defaultConfig.portletArea = null;
Twinkle.defaultConfig.portletId = 'p-cactions';
Twinkle.defaultConfig.portletName = null;
Twinkle.defaultConfig.portletType = null;
Twinkle.defaultConfig.portletNext = null;
}
Twinkle.getPref = function twinkleGetPref(name) {
if (typeof Twinkle.prefs === 'object' && Twinkle.prefs[name] !== undefined) {
return Twinkle.prefs[name];
}
// Old preferences format, used before twinkleoptions.js was a thing
if (typeof window.TwinkleConfig === 'object' && window.TwinkleConfig[name] !== undefined) {
return window.TwinkleConfig[name];
}
if (typeof window.FriendlyConfig === 'object' && window.FriendlyConfig[name] !== undefined) {
return window.FriendlyConfig[name];
}
return Twinkle.defaultConfig[name];
};
/**
* **************** Twinkle.addPortlet() ****************
*
* Adds a portlet menu to one of the navigation areas on the page.
* This is necessarily quite a hack since skins, navigation areas, and
* portlet menu types all work slightly different.
*
* Available navigation areas depend on the skin used.
* Vector:
* For each option, the outer div class contains "vector-menu", the inner div class is "vector-menu-content", and the ul is "vector-menu-content-list"
* "mw-panel", outer div class contains "vector-menu-portal". Existing portlets/elements: "p-logo", "p-navigation", "p-interaction", "p-tb", "p-coll-print_export"
* "left-navigation", outer div class contains "vector-menu-tabs" or "vector-menu-dropdown". Existing portlets: "p-namespaces", "p-variants" (menu)
* "right-navigation", outer div class contains "vector-menu-tabs" or "vector-menu-dropdown". Existing portlets: "p-views", "p-cactions" (menu), "p-search"
* Special layout of p-personal portlet (part of "head") through specialized styles.
* Monobook:
* "column-one", outer div class "portlet", inner div class "pBody". Existing portlets: "p-cactions", "p-personal", "p-logo", "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export"
* Special layout of p-cactions and p-personal through specialized styles.
* Modern:
* "mw_contentwrapper" (top nav), outer div class "portlet", inner div class "pBody". Existing portlets or elements: "p-cactions", "mw_content"
* "mw_portlets" (sidebar), outer div class "portlet", inner div class "pBody". Existing portlets: "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export"
*
* @param String navigation -- id of the target navigation area (skin dependant, on vector either of "left-navigation", "right-navigation", or "mw-panel")
* @param String id -- id of the portlet menu to create, preferably start with "p-".
* @param String text -- name of the portlet menu to create. Visibility depends on the class used.
* @param String type -- type of portlet. Currently only used for the vector non-sidebar portlets, pass "menu" to make this portlet a drop down menu.
* @param Node nextnodeid -- the id of the node before which the new item should be added, should be another item in the same list, or undefined to place it at the end.
*
* @return Node -- the DOM node of the new item (a DIV element) or null
*/
Twinkle.addPortlet = function(navigation, id, text, type, nextnodeid) {
// sanity checks, and get required DOM nodes
var root = document.getElementById(navigation) || document.querySelector(navigation);
if (!root) {
return null;
}
var item = document.getElementById(id);
if (item) {
if (item.parentNode && item.parentNode === root) {
return item;
}
return null;
}
var nextnode;
if (nextnodeid) {
nextnode = document.getElementById(nextnodeid);
}
// verify/normalize input
var skin = mw.config.get('skin');
if (skin !== 'vector' || (navigation !== 'left-navigation' && navigation !== 'right-navigation')) {
type = null; // menu supported only in vector's #left-navigation & #right-navigation
}
var outerDivClass, innerDivClass;
switch (skin) {
case 'vector':
// XXX: portal doesn't work
if (navigation !== 'portal' && navigation !== 'left-navigation' && navigation !== 'right-navigation') {
navigation = 'mw-panel';
}
outerDivClass = 'vector-menu vector-menu-' + (navigation === 'mw-panel' ? 'portal' : type === 'menu' ? 'dropdown' : 'tabs');
innerDivClass = 'vector-menu-content';
break;
case 'modern':
if (navigation !== 'mw_portlets' && navigation !== 'mw_contentwrapper') {
navigation = 'mw_portlets';
}
outerDivClass = 'portlet';
break;
case 'timeless':
outerDivClass = 'mw-portlet';
innerDivClass = 'mw-portlet-body';
break;
default:
navigation = 'column-one';
outerDivClass = 'portlet';
break;
}
// Build the DOM elements.
var outerDiv = document.createElement('nav');
outerDiv.setAttribute('aria-labelledby', id + '-label');
// Vector getting vector-menu-empty FIXME TODO
outerDiv.className = outerDivClass + ' emptyPortlet';
outerDiv.id = id;
if (nextnode && nextnode.parentNode === root) {
root.insertBefore(outerDiv, nextnode);
} else {
root.appendChild(outerDiv);
}
var h3 = document.createElement('h3');
h3.id = id + '-label';
var ul = document.createElement('ul');
if (skin === 'vector') {
// add invisible checkbox to keep menu open when clicked
// similar to the p-cactions ("More") menu
if (outerDivClass.indexOf('vector-menu-dropdown') !== -1) {
var chkbox = document.createElement('input');
chkbox.className = 'vectorMenuCheckbox vector-menu-checkbox'; // remove vectorMenuCheckbox after 1.35-wmf.37 goes live
chkbox.setAttribute('type', 'checkbox');
chkbox.setAttribute('aria-labelledby', id + '-label');
outerDiv.appendChild(chkbox);
var span = document.createElement('span');
span.appendChild(document.createTextNode(text));
h3.appendChild(span);
var a = document.createElement('a');
a.href = '#';
$(a).click(function(e) {
e.preventDefault();
});
h3.appendChild(a);
}
outerDiv.appendChild(h3);
ul.className = 'menu vector-menu-content-list'; // remove menu after 1.35-wmf.37 goes live
} else {
h3.appendChild(document.createTextNode(text));
outerDiv.appendChild(h3);
}
if (innerDivClass) {
var innerDiv = document.createElement('div');
innerDiv.className = innerDivClass;
innerDiv.appendChild(ul);
outerDiv.appendChild(innerDiv);
} else {
outerDiv.appendChild(ul);
}
return outerDiv;
};
/**
* **************** Twinkle.addPortletLink() ****************
* Builds a portlet menu if it doesn't exist yet, and add the portlet link.
* @param task: Either a URL for the portlet link or a function to execute.
*/
Twinkle.addPortletLink = function(task, text, id, tooltip) {
if (Twinkle.getPref('portletArea') !== null) {
Twinkle.addPortlet(Twinkle.getPref('portletArea'), Twinkle.getPref('portletId'), Twinkle.getPref('portletName'), Twinkle.getPref('portletType'), Twinkle.getPref('portletNext'));
}
var link = mw.util.addPortletLink(Twinkle.getPref('portletId'), typeof task === 'string' ? task : '#', text, id, tooltip);
$('.client-js .skin-vector #p-cactions').css('margin-right', 'initial');
if (typeof task === 'function') {
$(link).click(function (ev) {
task();
ev.preventDefault();
});
}
if ($.collapsibleTabs) {
$.collapsibleTabs.handleResize();
}
return link;
};
/**
* **************** General initialization code ****************
*/
var scriptpathbefore = mw.util.wikiScript('index') + '?title=',
scriptpathafter = '&action=raw&ctype=text/javascript&happy=yes';
// Retrieve the user's Twinkle preferences
$.ajax({
url: scriptpathbefore + 'User:' + encodeURIComponent(mw.config.get('wgUserName')) + '/twinkleoptions.js' + scriptpathafter,
dataType: 'text'
})
.fail(function () {
mw.notify('Could not load your Twinkle preferences', {type: 'error'});
})
.done(function (optionsText) {
// Quick pass if user has no options
if (optionsText === '') {
return;
}
// Twinkle options are basically a JSON object with some comments. Strip those:
optionsText = optionsText.replace(/(?:^(?:\/\/[^\n]*\n)*\n*|(?:\/\/[^\n]*(?:\n|$))*$)/g, '');
// First version of options had some boilerplate code to make it eval-able -- strip that too. This part may become obsolete down the line.
if (optionsText.lastIndexOf('window.Twinkle.prefs = ', 0) === 0) {
optionsText = optionsText.replace(/(?:^window.Twinkle.prefs = |;\n*$)/g, '');
}
try {
var options = JSON.parse(optionsText);
if (options) {
if (options.twinkle || options.friendly) { // Old preferences format
Twinkle.prefs = $.extend(options.twinkle, options.friendly);
} else {
Twinkle.prefs = options;
}
// v2 established after unification of Twinkle/Friendly objects
Twinkle.prefs.optionsVersion = Twinkle.prefs.optionsVersion || 1;
}
} catch (e) {
mw.notify('Could not parse your Twinkle preferences', {type: 'error'});
}
})
.always(function () {
$(Twinkle.load);
});
// Developers: you can import custom Twinkle modules here
// For example, mw.loader.load(scriptpathbefore + "User:UncleDouggie/morebits-test.js" + scriptpathafter);
Twinkle.load = function () {
// Don't activate on special pages other than those on the whitelist so that
// they load faster, especially the watchlist.
var specialPageWhitelist = [ 'Block', 'Contributions', 'Recentchanges', 'Recentchangeslinked' ]; // wgRelevantUserName defined for non-sysops on Special:Block
if (Morebits.userIsSysop) {
specialPageWhitelist = specialPageWhitelist.concat([ 'DeletedContributions', 'Prefixindex' ]);
}
if (mw.config.get('wgNamespaceNumber') === -1 &&
specialPageWhitelist.indexOf(mw.config.get('wgCanonicalSpecialPageName')) === -1) {
return;
}
// Prevent clickjacking
if (window.top !== window.self) {
return;
}
// Set custom Api-User-Agent header, for server-side logging purposes
Morebits.wiki.api.setApiUserAgent('Twinkle/2.0 (' + mw.config.get('wgDBname') + ')');
// Load all the modules in the order that the tabs should appear
var twinkleModules = [
// User/user talk-related
'warn', 'arv', 'block', 'welcome', 'shared', 'talkback',
// Deletion
'speedy', 'prod', 'deprod',
// Maintenance
'protect', 'tag',
// Misc. ones last
'diff', 'unlink', 'fluff', 'batchdelete', 'batchprotect', 'batchundelete'
];
// Don't load modules users have disabled
var disabledModules = Twinkle.getPref('disabledModules').concat(Twinkle.getPref('disabledSysopModules'));
twinkleModules.filter(function(mod) {
return disabledModules.indexOf(mod) === -1;
}).forEach(function(module) {
Twinkle[module]();
});
Twinkle.config.init(); // Can't turn off
// Run the initialization callbacks for any custom modules
Twinkle.initCallbacks.forEach(function (func) {
func();
});
Twinkle.addInitCallback = function (func) {
func();
};
// Increases text size in Twinkle dialogs, if so configured
if (Twinkle.getPref('dialogLargeFont')) {
mw.util.addCSS('.morebits-dialog-content, .morebits-dialog-footerlinks { font-size: 100% !important; } ' +
'.morebits-dialog input, .morebits-dialog select, .morebits-dialog-content button { font-size: inherit !important; }');
}
// Hide the lingering space if the TW menu is empty
if (mw.config.get('skin') === 'vector' && Twinkle.getPref('portletType') === 'menu' && $('#p-twinkle').length === 0) {
$('#p-cactions').css('margin-right', 'initial');
}
};
}(window, document, jQuery)); // End wrap with anonymous function
// </nowiki>
/* ===== twinkleconfig.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinkleconfig.js: Preferences module
****************************************
* Mode of invocation: Adds configuration form to Wikipedia:Twinkle/Preferences,
and adds an ad box to the top of user subpages belonging to the
currently logged-in user which end in '.js'
* Active on: What I just said. Yeah.
I, [[User:This, that and the other]], originally wrote this. If the code is misbehaving, or you have any
questions, don't hesitate to ask me. (This doesn't at all imply [[WP:OWN]]ership - it's just meant to
point you in the right direction.) -- TTO
*/
Twinkle.config = {};
Twinkle.config.watchlistEnums = { yes: 'به فهرست پیگیری اضافه کن', no: "به فهرست پیگیری اضافه نکن", 'default': 'از تنظیمات وبگاه تبعیت کن' }; // localized
Twinkle.config.commonSets = {
csdCriteria: {
'حذف سریع': 'حذف سریع',
'خرابکاری': 'خرابکاری',
'حذف شده': 'حذف شده',
'نقض حق تکثیر': 'نقض حق تکثیر',
'تکراری': 'تکراری',
'استفاده نشده': 'استفاده نشده',
'ویکیمدیا': 'ویکیمدیا',
'بحث یتیم': 'بحث یتیم'
},
csdCriteriaDisplayOrder: [
'حذف سریع',
'خرابکاری',
'حذف شده',
'نقض حق تکثیر',
'تکراری',
'استفاده نشده',
'ویکیمدیا',
'بحث یتیم'
],
csdCriteriaNotification: {
'حذف سریع': 'حذف سریع',
'خرابکاری': 'خرابکاری',
'حذف شده': 'حذف شده',
'نقض حق تکثیر': 'نقض حق تکثیر',
'تکراری': 'تکراری',
'استفاده نشده': 'استفاده نشده',
'ویکیمدیا': 'ویکیمدیا',
'بحث یتیم': 'بحث یتیم'
},
csdCriteriaNotificationDisplayOrder: [
'حذف سریع',
'خرابکاری',
'حذف شده',
'نقض حق تکثیر',
'تکراری',
'استفاده نشده',
'ویکیمدیا',
'بحث یتیم'
],
csdAndDICriteria: {
'حذف سریع': 'حذف سریع',
'خرابکاری': 'خرابکاری',
'حذف شده': 'حذف شده',
'نقض حق تکثیر': 'نقض حق تکثیر',
'تکراری': 'تکراری',
'استفاده نشده': 'استفاده نشده',
'ویکیمدیا': 'ویکیمدیا',
'بحث یتیم': 'بحث یتیم'
},
csdAndDICriteriaDisplayOrder: [
'حذف سریع',
'خرابکاری',
'حذف شده',
'نقض حق تکثیر',
'تکراری',
'استفاده نشده',
'ویکیمدیا',
'بحث یتیم'
],
namespacesNoSpecial: {
'0': 'مقاله', // localized
'1': 'بحث (مقاله)', // localized
'2': 'کاربر', // localized
'3': 'بحث کاربر', // localized
'4': 'ویکیواژه', // localized
'5': 'بحث ویکیواژه', // localized
'6': 'پرونده', // localized
'7': 'بحث پرونده', // localized
'8': 'مدیاویکی', // localized
'9': 'بحث مدیاویکی', // localized
'10': 'الگو', // localized
'11': 'بحث الگو', // localized
'12': 'راهنما', // localized
'13': 'بحث راهنما', // localized
'14': 'رده', // localized
'15': 'بحث رده', // localized
'100': 'درگاه', // localized
'101': 'بحث درگاه', // localized
'108': 'کتاب', // localized
'109': 'بحث کتاب', // localized
'118': 'پیشنویس', // localized
'119': 'بحث پیشنویس', // localized
// '710': 'TimedText', // localized
// '711': 'TimedText talk', // localized
'828': 'پودمان', // localized
'829': 'بحث پودمان' // localized
}
};
/**
* Section entry format:
*
* {
* title: <human-readable section title>,
* adminOnly: <true for admin-only sections>,
* hidden: <true for advanced preferences that rarely need to be changed - they can still be modified by manually editing twinkleoptions.js>,
* preferences: [
* {
* name: <TwinkleConfig property name>,
* label: <human-readable short description - used as a form label>,
* helptip: <(optional) human-readable text (using valid HTML) that complements the description, like limits, warnings, etc.>
* adminOnly: <true for admin-only preferences>,
* type: <string|boolean|integer|enum|set|customList> (customList stores an array of JSON objects { value, label }),
* enumValues: <for type = "enum": a JSON object where the keys are the internal names and the values are human-readable strings>,
* setValues: <for type = "set": a JSON object where the keys are the internal names and the values are human-readable strings>,
* setDisplayOrder: <(optional) for type = "set": an array containing the keys of setValues (as strings) in the order that they are displayed>,
* customListValueTitle: <for type = "customList": the heading for the left "value" column in the custom list editor>,
* customListLabelTitle: <for type = "customList": the heading for the right "label" column in the custom list editor>
* },
* . . .
* ]
* },
* . . .
*
*/
Twinkle.config.sections = [
{
title: 'عمومی', // localized
preferences: [
// TwinkleConfig.summaryAd (string)
// Text to be appended to the edit summary of edits made using Twinkle
{
name: 'summaryAd',
label: "پسوند تبلیغی جهت ضمیمهشدن به خلاصه ویرایشهای توینکل", // localized
helptip: 'خلاصهٔ پیشرفته باید با یک فاصله آغاز شده و تا حد امکان کوتاه نوشتهشود.', // localized (to Huji: Summary Ad را به خلاصهٔ پیشرفته ترجمه کردم، چون «تبلیغ در خلاصه ویرایش منطقی نبود؛ آیا خلاصهٔ پیشرفته ترجمهٔ دقیقی است؟)
type: 'string'
},
// TwinkleConfig.deletionSummaryAd (string)
// Text to be appended to the edit summary of deletions made using Twinkle
{
name: 'deletionSummaryAd',
label: 'خلاصهٔ مورد استفاده در خلاصه ویرایش حذف', // localized
helptip: 'معمولاً برابر با همان خلاصه ویرایش پیشرفته در بالا.', // localized
adminOnly: true,
type: 'string'
},
// TwinkleConfig.protectionSummaryAd (string)
// Text to be appended to the edit summary of page protections made using Twinkle
{
name: 'protectionSummaryAd',
label: 'خلاصهٔ پیشرفته برای استفاده در خلاصه ویرایش محافظت صفحه', // localized
helptip: 'معمولاً برابر با همان خلاصه ویرایش پیشرفته در بالا.', // localized
adminOnly: true,
type: 'string'
},
// TwinkleConfig.userTalkPageMode may take arguments:
// 'window': open a new window, remember the opened window
// 'tab': opens in a new tab, if possible.
// 'blank': force open in a new window, even if such a window exists
{
name: 'userTalkPageMode',
label: 'باز کردن بحث کاربر در ...', // localized
type: 'enum',
enumValues: { window: 'یک پنجره، بهجای بحث کاربران', tab: 'یک زبانهٔ جدید', blank: 'یک پنجرهٔ کاملاً جدید' } // localized
},
// TwinkleConfig.dialogLargeFont (boolean)
{
name: 'dialogLargeFont',
label: 'استفاده از متن بزرگتر در پیامهای توینکل', // localized
type: 'boolean'
},
// Twinkle.config.disabledModules (array)
{
name: 'disabledModules',
label: 'خاموشکردن پودمانهای انتخابشدهٔ توینکل', // localized
helptip: 'هر آنچه در اینجا انتخاب کنید، برای استفاده در دسترس نخواهد بود، پس با دقت عمل کنید. برای فعالکردن دوباره، گزینهها را غیرفعال کنید.', // localized
type: 'set',
setValues: { /*arv: 'ARV',*/ warn: 'هشدار', welcome: 'خوش آمدید', shared: 'نشانی آیپی مشترک', talkback: 'بازبحث', speedy: 'حذف سریع', prod: 'حذف زماندار', xfd: 'نظرخواهی حذف', image: 'تصویر', protect: 'محافظت (دمص)', tag: 'برچسب', diff: 'تفاوت', unlink: 'حذف پیوند', 'fluff': 'بازگردانی و واگردانی' } // localized
},
// Twinkle.config.disabledSysopModules (array)
{
name: 'disabledSysopModules',
label: 'خاموش کردن پودمانهای ویژهٔ مدیران', // localized
helptip: 'هر آنچه در اینجا انتخاب کنید، برای استفاده در دسترس نخواهد بود، پس با دقت عمل کنید. برای فعالکردن دوباره، گزینهها را غیرفعال کنید.', // localized
adminOnly: true,
type: 'set',
setValues: { block: 'بستن', deprod: 'مخالفت با حذف زماندار (مخزمان)', batchdelete: 'حذف دستهجمعی', batchprotect: 'محافظت دستهجمعی', batchundelete: 'احیای دستهجمعی' } // localized
}
]
},
/*
{
title: 'ARV',
preferences: [
{
name: 'spiWatchReport',
label: 'افزودن صفحههای گزارش زاپاسبازی به فهرست پیگیری', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
}
]
},
*/ // localized [removed]
{
title: 'بستن کاربر', // localized
adminOnly: true,
preferences: [
// TwinkleConfig.defaultToPartialBlocks (boolean)
// Whether to default partial blocks on or off
{
name: 'defaultToPartialBlocks',
label: 'به صورت پیشفرض در زمان بازکردن منوی بستن، قطع دسترسی موردی انتخاب شود', // localized
type: 'boolean'
},
// TwinkleConfig.blankTalkpageOnIndefBlock (boolean)
// if true, blank the talk page when issuing an indef block notice (per [[WP:UWUL#Indefinitely blocked users]])
{
name: 'blankTalkpageOnIndefBlock',
label: 'صفحه بحث را در زمان قطع دسترسی بیپایان پاک شود', // localized
helptip: 'برای اطلاعات بیشتر، <a href="' + mw.util.getUrl('en:Wikipedia:WikiProject_User_warnings/Usage_and_layout#Indefinitely_blocked_users') + '">WP:UWUL</a> را ببینید.', // localized
type: 'boolean'
}
]
},
{
title: 'حذف تصویر', // localized
preferences: [
// TwinkleConfig.notifyUserOnDeli (boolean)
// If the user should be notified after placing a file deletion tag
{
name: 'notifyUserOnDeli',
label: '"اطلاعرسانی به بارگذار پرونده" به صورت پیشفرض فعال باشد', // localized
type: 'boolean'
},
// TwinkleConfig.deliWatchPage (string)
// The watchlist setting of the page tagged for deletion. Either "yes", "no", or "default". Default is "default" (Duh).
{
name: 'deliWatchPage',
label: 'تصویر در زمان برچسبزدن به فهرست پیگیری اضافه شود', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
// TwinkleConfig.deliWatchUser (string)
// The watchlist setting of the user talk page if a notification is placed. Either "yes", "no", or "default". Default is "default" (Duh).
{
name: 'deliWatchUser',
label: 'صفحه بحث بارگذار پرونده در زمان اطلاعرسانی، به فهرست پیگیری اضافه شود', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
}
]
},
{
title: 'حذف زماندار', // localized
preferences: [
// TwinkleConfig.watchProdPages (boolean)
// If, when applying prod template to page, to watch the page
{
name: 'watchProdPages',
label: 'مقاله در زمان برچسبزدن به فهرست پیگیری اضافه شود', // localized
type: 'boolean'
},
// TwinkleConfig.markProdPagesAsPatrolled (boolean)
// If, when applying prod template to page, to mark the page as curated/patrolled (if the page was reached from NewPages)
{
name: 'markProdPagesAsPatrolled',
label: 'علامت زدن صفحه بهعنوان گشتخورده/بازبینیشده در زمان برچسب زدن (در صورت امکان)', // localized
helptip: 'از آنجا که این عمل بر خلاف اجماع برای بهترین راهکار است، در بیشتر موارد نباید این گزینه را انتخاب کرد', // localized
type: 'boolean'
},
// TwinkleConfig.prodReasonDefault (string)
// The prefilled PROD reason.
{
name: 'prodReasonDefault',
label: 'دلیل پیشفرض حذف زماندار', // localized
type: 'string'
},
{
name: 'logProdPages',
label: 'نگهداری سیاههای از همهٔ صفحههایی که نامزد حذف زماندار کردهاید در زیرصفحه کاربری', // localized
helptip: 'از آنجا که کاربران غیرمدیر به مشارکتهای حذفشدهٔ خود دسترسی ندارند، سیاههٔ نگهداریشده در زیرصفحهٔ کاربری میتواند برای پیگیری تمام صفحههایی که با استفاده از توینکل نامزد حذف زماندار کردهاید، مفید باشد.', // localized
type: 'boolean'
},
{
name: 'prodLogPageName',
label: 'نگهداری سیاههٔ حذف زماندار در این زیرصفحهٔ کاربری', // localized
helptip: 'نام یک زیرصفحه را در این جعبه وارد کنید. میتوانید در صفحهٔ کاربر:<i>نام کاربری</i>/<i>نام زیرصفحه</i> به سیاههٔ حذف زماندار خود دسترسی داشته باشید. این قابلیت تنها در صورتی کار میکند که گزینهٔ سیاههٔ حذف زماندار در زیرصفحه کاربری را فعال کرده باشید.', // localized
type: 'string'
}
]
},
{
title: 'برگرداندن و واگردانی', // twinklefluff module // localized
preferences: [
// TwinkleConfig.autoMenuAfterRollback (bool)
// Option to automatically open the warning menu if the user talk page is opened post-reversion
{
name: 'autoMenuAfterRollback',
label: 'باز کردن خودکار منوی هشدار توینکل در صفحهٔ بحث کاربر پس از بازگردانی با توینکل', // localized
helptip: 'تنها در صورتی کار میکند که گزینهٔ مرتبط در پایین را فعال کرده باشید.', // localized
type: 'boolean'
},
// TwinkleConfig.openTalkPage (array)
// What types of actions that should result in opening of talk page
{
name: 'openTalkPage',
label: 'باز کردن بحث کاربر پس از انجام این گونههای بازبینی', // localized
type: 'set',
setValues: { agf: 'برگردانی با فرض حسن نیت', norm: 'برگردانی عادی', vand: 'برگردانی خرابکاری', torev: '«برگرداندن این نسخه»' } // localized
},
// TwinkleConfig.openTalkPageOnAutoRevert (bool)
// Defines if talk page should be opened when calling revert from contribs or recent changes pages. If set to true, openTalkPage defines then if talk page will be opened.
{
name: 'openTalkPageOnAutoRevert',
label: 'باز کردن بحث کاربر بههنگام فراخواندن برگردانی از طریق مشارکتهای کاربر یا تغییرات اخیر', // localized
helptip: 'برای اینکه این قابلیت کار کند، در صورت فعال بودن این گزینه باید گزینههای مورد نظر در تنظیمات قبلی نیز فعال شده باشند.', // localized
type: 'boolean'
},
// TwinkleConfig.rollbackInPlace (bool)
//
{
name: 'rollbackInPlace',
label: "جلوگیری از بارگیری مجدد صفحه در زمان برگردانی از طریق مشارکتهای کاربر یا تغییرات اخیر", // localized
helptip: "در صورت فعال بودن این گزینه، توینکل صفحهٔ مشارکتهای کاربر یا تغییرات اخیر را پس از برگردانی مجدداً بارگیری نخواهد کرد. این به شما این امکان را میدهد که همزمان بیش از یک ویرایش را برگردانی کنید.", // localized
type: 'boolean'
},
// TwinkleConfig.markRevertedPagesAsMinor (array)
// What types of actions that should result in marking edit as minor
{
name: 'markRevertedPagesAsMinor',
label: 'علامتزدن این گونههای بازبینی بهعنوان ویرایش جزئی',
type: 'set',
setValues: { agf: 'برگردانی با فرض حسن نیت', norm: 'برگردانی عادی', vand: 'برگردانی خرابکاری', torev: '«برگرداندن این نسخه»' }
},
// TwinkleConfig.watchRevertedPages (array)
// What types of actions that should result in forced addition to watchlist
{
name: 'watchRevertedPages',
label: 'برای این انواع واگردانی، صفحه را به فهرست پیگیری بیفزا', // localized
type: 'set',
setValues: { agf: 'برگردانی با فرض حسن نیت', norm: 'برگردانی عادی', vand: 'برگردانی خرابکاری', torev: '«برگرداندن این نسخه»' } // localized
},
// TwinkleConfig.offerReasonOnNormalRevert (boolean)
// If to offer a prompt for extra summary reason for normal reverts, default to true
{
name: 'offerReasonOnNormalRevert',
label: 'درخواست دلیل برای برگردانیهای عادی', // localized
helptip: 'برگردانیهای «عادی» آنهایی هستند که از طریق پیوند [برگردانی] میانی فراخوانده شدهاند.', // localized
type: 'boolean'
},
{
name: 'confirmOnFluff',
label: 'قبل از بازگرداندن پیغام تایید نمایش داده شود', // localized
helptip: 'برای کاربرانی که از دستگاههای لمسی یا قلمی استفاده میکنند، و افرادی که بهطور مزمن، مردد هستند.', // localized
type: 'boolean'
},
// TwinkleConfig.showRollbackLinks (array)
// Where Twinkle should show rollback links:
// diff, others, mine, contribs, history, recent
// Note from TTO: |contribs| seems to be equal to |others| + |mine|, i.e. redundant, so I left it out heres
{
name: 'showRollbackLinks',
label: 'نمایش پیوندهای برگردانی در این صفحهها', // localized
type: 'set',
setValues: { diff: 'صفحههای تفاوت', others: 'صفحههای مشارکتهای کاربران دیگر', mine: 'صفحهٔ مشارکتهای من', recent: 'تغییرات اخیر و صفحههای ویژهٔ تغییرات مرتبط', history: 'صفحههای تاریخچه' } // localized
}
]
},
{
title: 'برچسب آیپی مشترک', // localized
preferences: [
{
name: 'markSharedIPAsMinor',
label: 'علامتگذاری برچسبزدن آیپی مشترک بهعنوان ویرایش جزئی', // localized
type: 'boolean'
}
]
},
{
title: 'حذف سریع', // localized
preferences: [
{
name: 'speedySelectionStyle',
label: 'چه زمانی صفحه حذف شود یا برچسب زده شود؟', // localized
type: 'enum',
enumValues: { 'buttonClick': 'وقتی روی دکمهٔ «ثبت» کلیک کردم', 'radioClick': 'به محض کلیک کردن روی گزینه' } // localized
},
// TwinkleConfig.watchSpeedyPages (array)
// Whether to add speedy tagged or deleted pages to watchlist
{
name: 'watchSpeedyPages',
label: 'برای این معیارها، صفحه را به فهرست پیگیری بیفزا', // localized
type: 'set',
setValues: Twinkle.config.commonSets.csdCriteria,
setDisplayOrder: Twinkle.config.commonSets.csdCriteriaDisplayOrder
},
// TwinkleConfig.markSpeedyPagesAsPatrolled (boolean)
// If, when applying speedy template to page, to mark the page as triaged/patrolled (if the page was reached from NewPages)
{
name: 'markSpeedyPagesAsPatrolled',
label: 'در زمان نصب برچسب، صفحه را در صورت امکان گشت بزن یا بازبینی کن', // localized
helptip: 'این گزینه احتمالاً نباید استفاده شود چون برای آن اجماع وجود ندارد', // localized
type: 'boolean'
},
// TwinkleConfig.welcomeUserOnSpeedyDeletionNotification (array of strings)
// On what types of speedy deletion notifications shall the user be welcomed
// with a "firstarticle" notice if their talk page has not yet been created.
{
name: 'welcomeUserOnSpeedyDeletionNotification',
label: 'وقتی کاربر را از برچسبهای مربوط به این معیارها آگاه میکنی به او خوشامد هم بگو', // localized
helptip: 'پیام خوشامد فقط وقتی افزوده میشود که صفحهٔ بحث کاربر وجود نداشته باشد. الگوی {{نخستین نوشتار}} برای این منظور به کار خواهد رفت.', // localized
type: 'set',
setValues: Twinkle.config.commonSets.csdCriteriaNotification,
setDisplayOrder: Twinkle.config.commonSets.csdCriteriaNotificationDisplayOrder
},
// TwinkleConfig.notifyUserOnSpeedyDeletionNomination (array)
// What types of actions should result in the author of the page being notified of nomination
{
name: 'notifyUserOnSpeedyDeletionNomination',
label: 'برای این معیارها، سازندهٔ صفحه را آگاه کن', // localized
helptip: 'وقتی گزینهٔ آگاه کردن سازندهٔ صفحه را انتخاب میکند، این آگاهسازی فقط برای معیارهای زیر رخ خواهد داد.', // localized
type: 'set',
setValues: Twinkle.config.commonSets.csdCriteriaNotification,
setDisplayOrder: Twinkle.config.commonSets.csdCriteriaNotificationDisplayOrder
},
// TwinkleConfig.warnUserOnSpeedyDelete (array)
// What types of actions should result in the author of the page being notified of speedy deletion (admin only)
{
name: 'warnUserOnSpeedyDelete',
label: 'آگاهسازی سازندهٔ صفحه در زمان حذف با این معیارها', // localized
helptip: 'حتی در صورتی که در صفحهٔ معیارهای حذف سریع آگاهسازی را فعال کنید، این آگاهسازی تنها برای معیارهایی که در اینجا انتخاب شدهاند انجام خواهد شد.', // localized
adminOnly: true,
type: 'set',
setValues: Twinkle.config.commonSets.csdCriteriaNotification,
setDisplayOrder: Twinkle.config.commonSets.csdCriteriaNotificationDisplayOrder
},
// TwinkleConfig.promptForSpeedyDeletionSummary (array of strings)
{
name: 'promptForSpeedyDeletionSummary',
label: 'فراهم کردن امکان ویرایش خلاصهٔ حذف در صورت حذف با این معیارها', // localized
adminOnly: true,
type: 'set',
setValues: Twinkle.config.commonSets.csdAndDICriteria,
setDisplayOrder: Twinkle.config.commonSets.csdAndDICriteriaDisplayOrder
},
// TwinkleConfig.deleteTalkPageOnDelete (boolean)
// If talk page if exists should also be deleted (CSD G8) when spedying a page (admin only)
{
name: 'deleteTalkPageOnDelete',
label: 'انتخاب گزینهٔ «صفحهٔ بحث نیز حذف شود» بهطور پیشفرض', // localized
adminOnly: true,
type: 'boolean'
},
{
name: 'deleteRedirectsOnDelete',
label: 'انتخاب گزینهٔ «تغییرمسیرها نیز حذف شوند» بهطور پیشفرض', // localized
adminOnly: true,
type: 'boolean'
},
// TwinkleConfig.deleteSysopDefaultToDelete (boolean)
// Make the CSD screen default to "delete" instead of "tag" (admin only)
{
name: 'deleteSysopDefaultToDelete',
label: 'فعالسازی پیشفرض حذف بیدرنگ بهجای قرار دادن برچسب حذف سریع', // localized
helptip: 'در صورت از پیش موجود بودن برچسب حذف سریع، جالت پیشفرض توینکل همیشه حالت «حذف» خواهد بود', // localized
adminOnly: true,
type: 'boolean'
},
// TwinkleConfig.speedyWindowWidth (integer)
// Defines the width of the Twinkle SD window in pixels
{
name: 'speedyWindowWidth',
label: 'عرض پنجرهٔ حذف سریع (به پیکسل)', // localized
type: 'integer'
},
// TwinkleConfig.speedyWindowWidth (integer)
// Defines the width of the Twinkle SD window in pixels
{
name: 'speedyWindowHeight',
label: 'ارتفاع پنجرهٔ حذف سریع (به پیکسل)', // localized
helptip: 'اگر یک نمایشگر بزرگ دارید، ممکن است بخواهید این اندازه را افزایش دهید.', // localized
type: 'integer'
},
{
name: 'logSpeedyNominations',
label: 'نگهداری یک سیاهه از نامزدیها برای حذف سریع در زیرصفحهٔ کاربری', // localized
helptip: 'از آنجا که کاربران غیرمدیر به مشارکتهای حذفشدهٔ خود دسترسی ندارند، سیاههٔ نگهداریشده در زیرصفحهٔ کاربری میتواند برای پیگیری تمام صفحههایی که با استفاده از توینکل نامزد حذف سریع کردهاید، مفید باشد. پروندههایی که با معیارهای حذف پرونده برچسب خوردهاند نیز به این سیاهه افزوده میشوند.', // localized
type: 'boolean'
},
{
name: 'speedyLogPageName',
label: 'نگهداری سیاههٔ حذف سریع در این زیرصفحهٔ کاربری', // localized
helptip: 'نام یک زیرصفحه را در این جعبه وارد کنید. میتوانید در صفحهٔ کاربر:<i>نام کربری</i>/<i>نام زیرصفحه</i> به سیاههٔ حذف سریع خود دسترسی داشته باشید. این قابلیت تنها در صورتی کار میکند که گزینهٔ سیاههٔ حذف سریع در زیرصفحه کاربری را فعال کرده باشید.', // localized
type: 'string'
},
{
name: 'noLogOnSpeedyNomination',
label: 'عدم افزودن صفحه به سیاههٔ حذف در فضای کاربری در زمان استفاده از این معیارها',
type: 'set',
setValues: Twinkle.config.commonSets.csdAndDICriteria,
setDisplayOrder: Twinkle.config.commonSets.csdAndDICriteriaDisplayOrder
}
]
},
{
title: 'برچسب', // localized
preferences: [
{
name: 'watchTaggedPages',
label: 'در زمان افزودن برچسب، صفحه را به فهرست پیگیری بیفزا', // localized
type: 'boolean'
},
{
name: 'watchMergeDiscussions',
label: 'افزودن صفحههای بحث به فهرست پیگیری در زمان آغاز نظرخواهی برای ادغام', // localized
type: 'boolean'
},
{
name: 'markTaggedPagesAsMinor',
label: 'علامتزدن ویرایشها برای افزودن برچسب بهعنوان ویرایش جزئی', // localized
type: 'boolean'
},
{
name: 'markTaggedPagesAsPatrolled',
label: 'انتخاب گزینهٔ «علامتگذاری صفحه بهعنوان گشتخورده/بازبینیشده» بهصورت پیشفرض', // localized
type: 'boolean'
},
{
name: 'groupByDefault',
label: 'انتخاب گزینهٔ «گروهبندی در {{مشکلات متعدد}}» بهصورت پیشفرض', // localized
type: 'boolean'
},
{
name: 'tagArticleSortOrder',
label: 'حالت پیشفرض برای مرتبسازی برچسبهای مقالهها', // localized
type: 'enum',
enumValues: { 'cat': 'بر پایه رده', 'alpha': 'به ترتیب الفبا' } // localized
},
{
name: 'customTagList',
label: 'برچسبهای نگهداری سفارشی مقاله/پیشنویس جهت نمایش', // localized
helptip: "این برچسبها بهعنوان گزینههای بیشتر در پایین فهرست برچسبها نمایش مییابند. بهعنوان مثال، میتوانید برچسبهای نگهداری اضافهای که هنوز به برچسبهای پیشفرض توینکل اضافه نشدهاند را بیافزایید.", // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در کادر برچسب' // localized
},
{
name: 'customFileTagList',
label: 'برچسبهای سفارشی نگهداری پرونده جهت نمایش', // localized
helptip: 'برچسبهای بیشتری که میخواهید برای پروندهها بیافزایید.', // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در کادر برچسب' // localized
},
{
name: 'customRedirectTagList',
label: 'برچسبهای سفارشی ردهٔ تغییرمسیر جهت نمایش', // localized
helptip: 'برچسبهای بیشتری که میخواهید برای تغییرمسیرها بیافزایید.', // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در کادر برچسب' // localized
}
]
},
{
title: 'گفتگو', // localized
preferences: [
{
name: 'markTalkbackAsMinor',
label: 'علامتگذاری بازبحثها بهعنوان ویرایش جزئی', // localized
type: 'boolean'
},
{
name: 'insertTalkbackSignature',
label: 'قرار دادن امضا در درون بازبحثها', // localized
type: 'boolean'
},
{
name: 'talkbackHeading',
label: 'عنوان بخش برای استفاده در بازبحثها', // localized
type: 'string'
},
{
name: 'adminNoticeHeading',
label: "عنوان بخش برای استفاده در گزارشهای ارسالشده در تابلوی اعلانات مدیران", // localized
helptip: 'تنها مرتبط با وو:تام.', // localized
type: 'string'
},
{
name: 'mailHeading',
label: "عنوان بخش برای استفاده در پیامهای \«ایمیل دارید\»", // localized
type: 'string'
}
]
},
{
title: 'زدودن پیوند', // localized
preferences: [
// TwinkleConfig.unlinkNamespaces (array)
// In what namespaces unlink should happen, default in 0 (article), 10 (template), 100 (portal), and 118 (draft)
{
name: 'unlinkNamespaces',
label: 'پیوندها را از صفحههایی که در این فضاهای نام هستند بزدا', // localized
helptip: 'از انتخاب فضاهای بحث پرهیز کنید، چون توینکل ممکن است پیوند به بایگانیهای بحث را حذف کند که کاری است به شدت نکوهش شده.', // localized
type: 'set',
setValues: Twinkle.config.commonSets.namespacesNoSpecial
}
]
},
{
title: 'هشدار به کاربر', // localized
preferences: [
// TwinkleConfig.defaultWarningGroup (int)
// Which level warning should be the default selected group, default is 1
{
name: 'defaultWarningGroup',
label: 'سطح پیشفرض هشدارها', // localized
type: 'enum',
enumValues: {
'1': 'سطح ۱', // localized
'2': 'سطح ۲', // localized
'3': 'سطح ۳', // localized
'4': 'سطح ۴', // localized
'5': 'سطح ۵ (فوری)', // localized
'6': 'اعلان راجع به یک مشکل واحد', // localized
'7': 'هشدار راجع به یک مشکل واحد', // localized
// 8 was used for block templates before #260
'9': 'هشدار سفارشی', // localized
'10': 'تمام الگوهای هشدار', // localized
'11': 'انتخاب خودکار (سطح ۱-۴)' // localized
}
},
// TwinkleConfig.combinedSingletMenus (boolean)
// if true, show one menu with both single-issue notices and warnings instead of two separately
{
name: 'combinedSingletMenus',
label: 'جایگزینی دو منوی جداگانه برای تکاشکال در یک منوی ترکیبشده', // localized
helptip: 'انتخاب اعلانهای تکاشکال یا هشدارهای تکاشکال بهعنوان گزینهٔ پیشفرض شما، در صورت فعال بودن، این را به انتخاب پیشفرض شما تبدیل خواهد کرد.', // localized
type: 'boolean'
},
// TwinkleConfig.showSharedIPNotice may take arguments:
// true: to show shared ip notice if an IP address
// false: to not print the notice
{
name: 'showSharedIPNotice',
label: 'افزودن اعلان اضافی در صفحههای بحث آیپی مشترک', // localized
helptip: 'اعلان مورد استفاده، {{آگاهی آیپی مشترک}} است', // localized
type: 'boolean'
},
// TwinkleConfig.watchWarnings (boolean)
// if true, watch the page which has been dispatched an warning or notice, if false, default applies
{
name: 'watchWarnings',
label: 'افزودن بحث کاربری به فهرست پیگیری در زمان ارسال اعلان', // localized
type: 'boolean'
},
// TwinkleConfig.oldSelect (boolean)
// if true, use the native select menu rather the select2-based one
{
name: 'oldSelect',
label: 'استفاده از منوی انتخاب کلاسیک غیر قابل جستجو', // localized
type: 'boolean'
},
{
name: 'customWarningList',
label: 'الگوهای هشدار سفارشی برای نمایش', // localized
helptip: 'میتوانید الگوهای شخصی یا زیرصفحههای کاربری را بیافزایید. هشدارهای سفارشی در ردهٔ «هشدارهای سفارشی» در داخل کادر جعبهٔ هشدار نمایش مییابند.', // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در فهرست هشدار (برای خلاصه ویرایش نیز استفاده میشود)' // localized
}
]
},
{
title: 'خوشامدگویی', // localized
preferences: [
{
name: 'topWelcomes',
label: 'قرار دادن خوشامدهای در بالای محتوای موجود در صفحههای بحث کاربران', // localized
type: 'boolean'
},
{
name: 'watchWelcomes',
label: 'افزودن صفحههای بحث کاربران به فهرست پیگیری در زمان خوشامدگویی', // localized
helptip: 'انجام این کار، تجربهٔ شخصی شما از خوشامدگویی به یک کاربر را بهبود میبخشد - شما قادر خواهید بود تا واکنش کاربر بهعنوان یک تازهکار را ببینید و احتمالاً به او کمک کنید.', // localized
type: 'boolean'
},
{
name: 'insertUsername',
label: 'افزودن نام کربری شما به الگو (در صورت امکان)', // localized
helptip: "برخی الگوهای خوشامد دارای جملهٔ اولیهای مانند \«سلام، من <username> هستم. خوش آمدید\" یا مشابه آن هستند. اگر این گزینه را غیرفعال کنید، این الگوها نام کاربری شما را بهاین نحو نمایش نخواهند داد.", // localized
type: 'boolean'
},
{
name: 'quickWelcomeMode',
label: 'عملکرد پیوند «خوشامد» در صفحهٔ تفاوت', // localized
helptip: 'اگر حالت خوشامدگویی خودکار را انتخاب کنید، الگویی که در زیر مشخص کردهاید مورد استفاده قرار خواهد گرفت.', // localized
type: 'enum',
enumValues: { auto: 'خوشامدگویی خودکار', norm: 'الزام برای انتخاب یک الگو' } // localized
},
{
name: 'quickWelcomeTemplate',
label: 'الگوی مورد استفاده در زمان خوشامدگویی خودکار', // localized
helptip: 'نام یک الگوی خواشمد را بدون استفاده از آکولاد وارد کنید. پیوندی به مقالهٔ مورد نظر اضافه خواهد شد.', // localized
type: 'string'
},
{
name: 'customWelcomeList',
label: 'الگوهای خوشامد سفارشی برای نمایش', // localized
helptip: "میتوانید الگوهای خوشامد یا زیرصفحههای کاربری که الگوی خوشامدگویی هستند را اضافه کنید (همراه با پیشوند \"کاربر:\"). فراموش نکنید که این الگوها در صفحههای بحث کاربران جانشانی خواهند شد.", // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در کادر خوشامد' // localized
},
{
name: 'customWelcomeSignature',
label: 'امضای خودکار الگوهای خوشامد سفارشی', // localized
helptip: 'در صورتی که الگوهای خوشامد سفارشی شما شامل امضای یکپارچه در داخل الگو هستند، این گزینه را غیرفعال کنید.', // localized
type: 'boolean'
}
]
},
{
title: 'نظرخواهی برای حذف', // localized
preferences: [
{
name: 'logXfdNominations',
label: 'نگهداری یک سیاهه از نامزدیها در نظرخواهی برای حذف (نبح) توسط شما در زیرصفحهٔ کاربری', // localized
helptip: 'سیاههٔ نگهداریشده در زیرصفحهٔ کاربری میتواند برای پیگیری تمام صفحههایی که با استفاده از توینکل نامزد نظرخواهی برای حذف کردهاید، مفید باشد.', // localized
type: 'boolean'
},
{
name: 'xfdLogPageName',
label: 'نگهداری سیاههٔ نظرخواهی برای حذف در این زیرصفحهٔ کاربری', // localized
helptip: 'نام یک زیرصفحه را در این جعبه وارد کنید. میتوانید در صفحهٔ کاربر:<i>نام کربری</i>/<i>نام زیرصفحه</i> به سیاههٔ نظرخواهی برای حذف خود دسترسی داشته باشید. این قابلیت تنها در صورتی کار میکند که گزینهٔ سیاههٔ نظرخواهی برای حذف در زیرصفحه کاربری را فعال کرده باشید.', // localized
type: 'string'
},
{
name: 'noLogOnXfdNomination',
label: 'برای نامزدیهای حذف در این دسته، سیاههای در زیرصفحهٔ کاربریام ایجاد نکن', // localized
type: 'set',
setValues: { afd: 'نبح'/*, tfd: 'TfD', ffd: 'FfD', cfd: 'CfD', cfds: 'CfD/S', mfd: 'MfD', rfd: 'RfD', rm: 'RM'*/ } // localized
},
// TwinkleConfig.xfdWatchPage (string)
// The watchlist setting of the page being nominated for XfD. Either "yes" (add to watchlist), "no" (don't
// add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
{
name: 'xfdWatchPage',
label: 'صفحهٔ نامزد حذف را به فهرست پیگیری اضافه کن', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
// TwinkleConfig.xfdWatchDiscussion (string)
// The watchlist setting of the newly created XfD page (for those processes that create discussion pages for each nomination),
// or the list page for the other processes.
// Either "yes" (add to watchlist), "no" (don't add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
{
name: 'xfdWatchDiscussion',
label: 'صفحهٔ نظرخواهی را به فهرست پیگیری اضافه کن', // localized
helptip: 'این گزینه مربوط به صفحهای است که در آن بحث راجع به حذف صورت میگیرد، یعنی صفحهٔ نظرخواهی', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
// TwinkleConfig.xfdWatchList (string)
// The watchlist setting of the XfD list page, *if* the discussion is on a separate page. Either "yes" (add to watchlist), "no" (don't
// add to watchlist), or "default" (use setting from preferences). Default is "no" (Hehe. Seriously though, who wants to watch it?
// Sorry in advance for any false positives.).
/*
{
name: 'xfdWatchList',
label: 'افزودن صفحهٔ سیاهه/فهرست روزانه به فهرست پیگیری (در صورت امکان)', // localized
helptip: 'این گزینه تنها در مورد وو:نبح اعمال خواهد شد که بحثهای آن در یک سیاههٔ روزانه یا صفحهٔ اصلی نبح تراگنجایش میشوند.', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
*/ // localized [removed]
// TwinkleConfig.xfdWatchUser (string)
// The watchlist setting of the user talk page if they receive a notification. Either "yes" (add to watchlist), "no" (don't
// add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
{
name: 'xfdWatchUser',
label: 'صفحه بحث کاربر یا سازندهٔ اولیهٔ صفحه را در هنگام اطلاعرسانی به فهرست پیگیری اضافه کن', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
// TwinkleConfig.xfdWatchRelated (string)
// The watchlist setting of the target of a redirect being nominated for RfD. Either "yes" (add to watchlist), "no" (don't
// add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
{
name: 'xfdWatchRelated',
label: "در زمان اطلاعرسانی، صفحه هدف تغییرمسیر را به فهرست پیگیری اضافه کن", // localized
helptip: 'این فقط برای زمانی کاربرد دارد که شما در صفحه بحث هدف تغییرمسیر اطلاعرسانی کردهاید.', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
{
name: 'markXfdPagesAsPatrolled',
label: 'در صورت امکان صفحه را در زمان ایجاد نظرخواهی برای حذف گشت بزن', // localized
type: 'boolean'
}
]
},
{
title: 'پنهان', // localized
hidden: true,
preferences: [
// twinkle.js: portlet setup
{
name: 'portletArea',
type: 'string'
},
{
name: 'portletId',
type: 'string'
},
{
name: 'portletName',
type: 'string'
},
{
name: 'portletType',
type: 'string'
},
{
name: 'portletNext',
type: 'string'
},
// twinklefluff.js: defines how many revision to query maximum, maximum possible is 50, default is 50
{
name: 'revertMaxRevisions',
type: 'integer'
},
// twinklewarn.js: When using the autolevel select option, how many days makes a prior warning stale
// Huggle is three days ([[Special:Diff/918980316]] and [[Special:Diff/919417999]]) while ClueBotNG is two:
// https://github.com/DamianZaremba/cluebotng/blob/4958e25d6874cba01c75f11debd2e511fd5a2ce5/bot/action_functions.php#L62
{
name: 'autolevelStaleDays',
type: 'integer'
},
// twinklebatchdelete.js: How many pages should be processed maximum
{
name: 'batchMax',
type: 'integer',
adminOnly: true
},
// twinklebatchdelete.js: How many pages should be processed at a time
{
name: 'batchdeleteChunks',
type: 'integer',
adminOnly: true
},
// twinklebatchprotect.js: How many pages should be processed at a time
{
name: 'batchProtectChunks',
type: 'integer',
adminOnly: true
},
// twinklebatchundelete.js: How many pages should be processed at a time
{
name: 'batchundeleteChunks',
type: 'integer',
adminOnly: true
},
// twinkledeprod.js: How many pages should be processed at a time
{
name: 'proddeleteChunks',
type: 'integer',
adminOnly: true
}
]
}
]; // end of Twinkle.config.sections
Twinkle.config.init = function twinkleconfigInit() {
// create the config page at Wikipedia:Twinkle/Preferences
if ((mw.config.get('wgNamespaceNumber') === mw.config.get('wgNamespaceIds').project && mw.config.get('wgTitle') === 'توینکل/ترجیحات') &&
mw.config.get('wgAction') === 'view') {
if (!document.getElementById('twinkle-config')) {
return; // maybe the page is misconfigured, or something - but any attempt to modify it will be pointless
}
// set style (the url() CSS function doesn't seem to work from wikicode - ?!)
document.getElementById('twinkle-config-titlebar').style.backgroundImage = 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB%2FqqA%2BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEhQTFRFr73ZobTPusjdsMHZp7nVwtDhzNbnwM3fu8jdq7vUt8nbxtDkw9DhpbfSvMrfssPZqLvVztbno7bRrr7W1d%2Fs1N7qydXk0NjpkW7Q%2BgAAADVJREFUeNoMwgESQCAAAMGLkEIi%2FP%2BnbnbpdB59app5Vdg0sXAoMZCpGoFbK6ciuy6FX4ABAEyoAef0BXOXAAAAAElFTkSuQmCC)';
var contentdiv = document.getElementById('twinkle-config-content');
contentdiv.textContent = ''; // clear children
// let user know about possible conflict with skin js/common.js file
// (settings in that file will still work, but they will be overwritten by twinkleoptions.js settings)
if (window.TwinkleConfig || window.FriendlyConfig) {
var contentnotice = document.createElement('p');
contentnotice.innerHTML = '<table class="plainlinks ombox ombox-content"><tr><td class="mbox-image">' +
'<img alt="" src="https://upload.wikimedia.org/wikipedia/commons/3/38/Imbox_content.png" /></td>' +
'<td class="mbox-text"><p><big><b>قبل از ویرایش تنظیمات در اینجا،</b> شما باید تنظیمات قبلی توینکل یا تنظیمات سفارشی تم خود را حذف کنید.</big></p>' + // localized
'<p>برای انجام آن شما میتوانید <a href="' + mw.util.getUrl('User:' + mw.config.get('wgUserName') + '/' + mw.config.get('skin') + // localized
'.js', { action: 'edit' }) + '" target="_blank"><b>فایل جاوااسکریپت شخصی خود</b></a> یا <a href="' + // localized
mw.util.getUrl('User:' + mw.config.get('wgUserName') + '/common.js', { action: 'edit'}) + '" target="_blank"><b>فایل common.js خود را ویرایش کرده و</b></a> همه خطهای کد که به <code>TwinkleConfig</code> و <code>FriendlyConfig</code> مرتبط است را حذف کنید.</p>' + // localized
'</td></tr></table>';
contentdiv.appendChild(contentnotice);
}
// start a table of contents
var toctable = document.createElement('div');
toctable.className = 'toc';
toctable.style.marginLeft = '0.4em';
// create TOC title
var toctitle = document.createElement('div');
toctitle.id = 'toctitle';
var toch2 = document.createElement('h2');
toch2.textContent = 'Contents ';
toctitle.appendChild(toch2);
// add TOC show/hide link
var toctoggle = document.createElement('span');
toctoggle.className = 'toctoggle';
toctoggle.appendChild(document.createTextNode('['));
var toctogglelink = document.createElement('a');
toctogglelink.className = 'internal';
toctogglelink.setAttribute('href', '#tw-tocshowhide');
toctogglelink.textContent = 'hide';
toctoggle.appendChild(toctogglelink);
toctoggle.appendChild(document.createTextNode(']'));
toctitle.appendChild(toctoggle);
toctable.appendChild(toctitle);
// create item container: this is what we add stuff to
var tocul = document.createElement('ul');
toctogglelink.addEventListener('click', function twinkleconfigTocToggle() {
var $tocul = $(tocul);
$tocul.toggle();
if ($tocul.find(':visible').length) {
toctogglelink.textContent = 'hide';
} else {
toctogglelink.textContent = 'show';
}
}, false);
toctable.appendChild(tocul);
contentdiv.appendChild(toctable);
var tocnumber = 1;
var contentform = document.createElement('form');
contentform.setAttribute('action', 'javascript:void(0)'); // was #tw-save - changed to void(0) to work around Chrome issue
contentform.addEventListener('submit', Twinkle.config.save, true);
contentdiv.appendChild(contentform);
var container = document.createElement('table');
container.style.width = '100%';
contentform.appendChild(container);
$(Twinkle.config.sections).each(function(sectionkey, section) {
if (section.hidden || (section.adminOnly && !Morebits.userIsSysop)) {
return true; // i.e. "continue" in this context
}
// add to TOC
var tocli = document.createElement('li');
tocli.className = 'toclevel-1';
var toca = document.createElement('a');
toca.setAttribute('href', '#twinkle-config-section-' + tocnumber.toString());
toca.appendChild(document.createTextNode(section.title));
tocli.appendChild(toca);
tocul.appendChild(tocli);
var row = document.createElement('tr');
var cell = document.createElement('td');
cell.setAttribute('colspan', '3');
var heading = document.createElement('h4');
heading.style.borderBottom = '1px solid gray';
heading.style.marginTop = '0.2em';
heading.id = 'twinkle-config-section-' + (tocnumber++).toString();
heading.appendChild(document.createTextNode(section.title));
cell.appendChild(heading);
row.appendChild(cell);
container.appendChild(row);
var rowcount = 1; // for row banding
// add each of the preferences to the form
$(section.preferences).each(function(prefkey, pref) {
if (pref.adminOnly && !Morebits.userIsSysop) {
return true; // i.e. "continue" in this context
}
row = document.createElement('tr');
row.style.marginBottom = '0.2em';
// create odd row banding
if (rowcount++ % 2 === 0) {
row.style.backgroundColor = 'rgba(128, 128, 128, 0.1)';
}
cell = document.createElement('td');
var label, input;
switch (pref.type) {
case 'boolean': // create a checkbox
cell.setAttribute('colspan', '2');
label = document.createElement('label');
input = document.createElement('input');
input.setAttribute('type', 'checkbox');
input.setAttribute('id', pref.name);
input.setAttribute('name', pref.name);
if (Twinkle.getPref(pref.name) === true) {
input.setAttribute('checked', 'checked');
}
label.appendChild(input);
label.appendChild(document.createTextNode(' ' + pref.label));
cell.appendChild(label);
break;
case 'string': // create an input box
case 'integer':
// add label to first column
cell.style.textAlign = 'right';
cell.style.paddingRight = '0.5em';
label = document.createElement('label');
label.setAttribute('for', pref.name);
label.appendChild(document.createTextNode(pref.label + ':'));
cell.appendChild(label);
row.appendChild(cell);
// add input box to second column
cell = document.createElement('td');
cell.style.paddingRight = '1em';
input = document.createElement('input');
input.setAttribute('type', 'text');
input.setAttribute('id', pref.name);
input.setAttribute('name', pref.name);
if (pref.type === 'integer') {
input.setAttribute('size', 6);
input.setAttribute('type', 'number');
input.setAttribute('step', '1'); // integers only
}
if (Twinkle.getPref(pref.name)) {
input.setAttribute('value', Twinkle.getPref(pref.name));
}
cell.appendChild(input);
break;
case 'enum': // create a combo box
// add label to first column
// note: duplicates the code above, under string/integer
cell.style.textAlign = 'right';
cell.style.paddingRight = '0.5em';
label = document.createElement('label');
label.setAttribute('for', pref.name);
label.appendChild(document.createTextNode(pref.label + ':'));
cell.appendChild(label);
row.appendChild(cell);
// add input box to second column
cell = document.createElement('td');
cell.style.paddingRight = '1em';
input = document.createElement('select');
input.setAttribute('id', pref.name);
input.setAttribute('name', pref.name);
$.each(pref.enumValues, function(enumvalue, enumdisplay) {
var option = document.createElement('option');
option.setAttribute('value', enumvalue);
if (Twinkle.getPref(pref.name) === enumvalue) {
option.setAttribute('selected', 'selected');
}
option.appendChild(document.createTextNode(enumdisplay));
input.appendChild(option);
});
cell.appendChild(input);
break;
case 'set': // create a set of check boxes
// add label first of all
cell.setAttribute('colspan', '2');
label = document.createElement('label'); // not really necessary to use a label element here, but we do it for consistency of styling
label.appendChild(document.createTextNode(pref.label + ':'));
cell.appendChild(label);
var checkdiv = document.createElement('div');
checkdiv.style.paddingLeft = '1em';
var worker = function(itemkey, itemvalue) {
var checklabel = document.createElement('label');
checklabel.style.marginRight = '0.7em';
checklabel.style.display = 'inline-block';
var check = document.createElement('input');
check.setAttribute('type', 'checkbox');
check.setAttribute('id', pref.name + '_' + itemkey);
check.setAttribute('name', pref.name + '_' + itemkey);
if (Twinkle.getPref(pref.name) && Twinkle.getPref(pref.name).indexOf(itemkey) !== -1) {
check.setAttribute('checked', 'checked');
}
// cater for legacy integer array values for unlinkNamespaces (this can be removed a few years down the track...)
if (pref.name === 'unlinkNamespaces') {
if (Twinkle.getPref(pref.name) && Twinkle.getPref(pref.name).indexOf(parseInt(itemkey, 10)) !== -1) {
check.setAttribute('checked', 'checked');
}
}
checklabel.appendChild(check);
checklabel.appendChild(document.createTextNode(itemvalue));
checkdiv.appendChild(checklabel);
};
if (pref.setDisplayOrder) {
// add check boxes according to the given display order
$.each(pref.setDisplayOrder, function(itemkey, item) {
worker(item, pref.setValues[item]);
});
} else {
// add check boxes according to the order it gets fed to us (probably strict alphabetical)
$.each(pref.setValues, worker);
}
cell.appendChild(checkdiv);
break;
case 'customList':
// add label to first column
cell.style.textAlign = 'right';
cell.style.paddingRight = '0.5em';
label = document.createElement('label');
label.setAttribute('for', pref.name);
label.appendChild(document.createTextNode(pref.label + ':'));
cell.appendChild(label);
row.appendChild(cell);
// add button to second column
cell = document.createElement('td');
cell.style.paddingRight = '1em';
var button = document.createElement('button');
button.setAttribute('id', pref.name);
button.setAttribute('name', pref.name);
button.setAttribute('type', 'button');
button.addEventListener('click', Twinkle.config.listDialog.display, false);
// use jQuery data on the button to store the current config value
$(button).data({
value: Twinkle.getPref(pref.name),
pref: pref
});
button.appendChild(document.createTextNode('ویرایش آیتمها')); // localized
cell.appendChild(button);
break;
default:
alert('twinkleconfig: unknown data type for preference ' + pref.name);
break;
}
row.appendChild(cell);
// add help tip
cell = document.createElement('td');
cell.style.fontSize = '90%';
cell.style.color = 'gray';
if (pref.helptip) {
// convert mentions of templates in the helptip to clickable links
cell.innerHTML = pref.helptip.replace(/{{(.+?)}}/g,
'{{<a href="' + mw.util.getUrl('Template:') + '$1" target="_blank">$1</a>}}');
}
// add reset link (custom lists don't need this, as their config value isn't displayed on the form)
if (pref.type !== 'customList') {
var resetlink = document.createElement('a');
resetlink.setAttribute('href', '#tw-reset');
resetlink.setAttribute('id', 'twinkle-config-reset-' + pref.name);
resetlink.addEventListener('click', Twinkle.config.resetPrefLink, false);
resetlink.style.cssFloat = 'right';
resetlink.style.margin = '0 0.6em';
resetlink.appendChild(document.createTextNode('بازگشت به مقدار پیشفرض')); // localized
cell.appendChild(resetlink);
}
row.appendChild(cell);
container.appendChild(row);
return true;
});
return true;
});
var footerbox = document.createElement('div');
footerbox.setAttribute('id', 'twinkle-config-buttonpane');
footerbox.style.backgroundColor = '#BCCADF';
footerbox.style.padding = '0.5em';
var button = document.createElement('button');
button.setAttribute('id', 'twinkle-config-submit');
button.setAttribute('type', 'submit');
button.appendChild(document.createTextNode('ذخیره کردن تغییرات')); // localized
footerbox.appendChild(button);
var footerspan = document.createElement('span');
footerspan.className = 'plainlinks';
footerspan.style.marginLeft = '2.4em';
footerspan.style.fontSize = '90%';
var footera = document.createElement('a');
footera.setAttribute('href', '#tw-reset-all');
footera.setAttribute('id', 'twinkle-config-resetall');
footera.addEventListener('click', Twinkle.config.resetAllPrefs, false);
footera.appendChild(document.createTextNode('بازگرداندن مقادیر پیشفرض')); // localized
footerspan.appendChild(footera);
footerbox.appendChild(footerspan);
contentform.appendChild(footerbox);
// since all the section headers exist now, we can try going to the requested anchor
if (location.hash) {
window.location.hash = location.hash;
}
} else if (mw.config.get('wgNamespaceNumber') === mw.config.get('wgNamespaceIds').user &&
mw.config.get('wgTitle').indexOf(mw.config.get('wgUserName')) === 0 &&
mw.config.get('wgPageName').slice(-3) === '.js') {
var box = document.createElement('div');
// Styled in twinkle.css
box.setAttribute('id', 'twinkle-config-headerbox');
var link,
scriptPageName = mw.config.get('wgPageName').slice(mw.config.get('wgPageName').lastIndexOf('/') + 1,
mw.config.get('wgPageName').lastIndexOf('.js'));
if (scriptPageName === 'twinkleoptions') {
// place "why not try the preference panel" notice
box.setAttribute('class', 'config-twopt-box');
if (mw.config.get('wgArticleId') > 0) { // page exists
box.appendChild(document.createTextNode('این صفحه شامل تنظیمات توینکل است. شما میتوانید آن را در ')); // localized
} else { // page does not exist
box.appendChild(document.createTextNode('شما میتوانید تنظیمات سفارشی خود برای توینکل را در ')); // localized
}
link = document.createElement('a');
link.setAttribute('href', mw.util.getUrl(mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').project] + ':توینکل/ترجیحات'));
link.appendChild(document.createTextNode('پنل تنظیمات توینکل')); // localized
box.appendChild(link);
box.appendChild(document.createTextNode('تغییر دهید یا این صفحه را ویرایش کنید.')); // localized
$(box).insertAfter($('#contentSub'));
} else if (['monobook', 'vector', 'cologneblue', 'modern', 'timeless', 'minerva', 'common'].indexOf(scriptPageName) !== -1) {
// place "Looking for Twinkle options?" notice
box.setAttribute('class', 'config-userskin-box');
box.appendChild(document.createTextNode('اگر میخواهید تنظیمات توینکل را تغییر دهید میتوانید از ')); // localized
link = document.createElement('a');
link.setAttribute('href', mw.util.getUrl(mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').project] + ':توینکل/ترجیحات'));
link.appendChild(document.createTextNode('پنل تنظیمات توینکل')); // localized
box.appendChild(link);
box.appendChild(document.createTextNode('استفاده کنید.')); // localized
$(box).insertAfter($('#contentSub'));
}
}
};
// custom list-related stuff
Twinkle.config.listDialog = {};
Twinkle.config.listDialog.addRow = function twinkleconfigListDialogAddRow(dlgtable, value, label) {
var contenttr = document.createElement('tr');
// "remove" button
var contenttd = document.createElement('td');
var removeButton = document.createElement('button');
removeButton.setAttribute('type', 'button');
removeButton.addEventListener('click', function() {
$(contenttr).remove();
}, false);
removeButton.textContent = 'حذف'; // localized
contenttd.appendChild(removeButton);
contenttr.appendChild(contenttd);
// value input box
contenttd = document.createElement('td');
var input = document.createElement('input');
input.setAttribute('type', 'text');
input.className = 'twinkle-config-customlist-value';
input.style.width = '97%';
if (value) {
input.setAttribute('value', value);
}
contenttd.appendChild(input);
contenttr.appendChild(contenttd);
// label input box
contenttd = document.createElement('td');
input = document.createElement('input');
input.setAttribute('type', 'text');
input.className = 'twinkle-config-customlist-label';
input.style.width = '98%';
if (label) {
input.setAttribute('value', label);
}
contenttd.appendChild(input);
contenttr.appendChild(contenttd);
dlgtable.appendChild(contenttr);
};
Twinkle.config.listDialog.display = function twinkleconfigListDialogDisplay(e) {
var $prefbutton = $(e.target);
var curvalue = $prefbutton.data('value');
var curpref = $prefbutton.data('pref');
var dialog = new Morebits.simpleWindow(720, 400);
dialog.setTitle(curpref.label);
dialog.setScriptName('تنظیمات توینکل'); // localized
var dialogcontent = document.createElement('div');
var dlgtable = document.createElement('table');
dlgtable.className = 'wikitable';
dlgtable.style.margin = '1.4em 1em';
dlgtable.style.width = 'auto';
var dlgtbody = document.createElement('tbody');
// header row
var dlgtr = document.createElement('tr');
// top-left cell
var dlgth = document.createElement('th');
dlgth.style.width = '5%';
dlgtr.appendChild(dlgth);
// value column header
dlgth = document.createElement('th');
dlgth.style.width = '35%';
dlgth.textContent = curpref.customListValueTitle ? curpref.customListValueTitle : 'Value';
dlgtr.appendChild(dlgth);
// label column header
dlgth = document.createElement('th');
dlgth.style.width = '60%';
dlgth.textContent = curpref.customListLabelTitle ? curpref.customListLabelTitle : 'Label';
dlgtr.appendChild(dlgth);
dlgtbody.appendChild(dlgtr);
// content rows
var gotRow = false;
$.each(curvalue, function(k, v) {
gotRow = true;
Twinkle.config.listDialog.addRow(dlgtbody, v.value, v.label);
});
// if there are no values present, add a blank row to start the user off
if (!gotRow) {
Twinkle.config.listDialog.addRow(dlgtbody);
}
// final "add" button
var dlgtfoot = document.createElement('tfoot');
dlgtr = document.createElement('tr');
var dlgtd = document.createElement('td');
dlgtd.setAttribute('colspan', '3');
var addButton = document.createElement('button');
addButton.style.minWidth = '8em';
addButton.setAttribute('type', 'button');
addButton.addEventListener('click', function() {
Twinkle.config.listDialog.addRow(dlgtbody);
}, false);
addButton.textContent = 'اضافهکردن'; // localized
dlgtd.appendChild(addButton);
dlgtr.appendChild(dlgtd);
dlgtfoot.appendChild(dlgtr);
dlgtable.appendChild(dlgtbody);
dlgtable.appendChild(dlgtfoot);
dialogcontent.appendChild(dlgtable);
// buttonpane buttons: [Save changes] [Reset] [Cancel]
var button = document.createElement('button');
button.setAttribute('type', 'submit'); // so Morebits.simpleWindow puts the button in the button pane
button.addEventListener('click', function() {
Twinkle.config.listDialog.save($prefbutton, dlgtbody);
dialog.close();
}, false);
button.textContent = 'ذخیره تغییرات'; // localized
dialogcontent.appendChild(button);
button = document.createElement('button');
button.setAttribute('type', 'submit'); // so Morebits.simpleWindow puts the button in the button pane
button.addEventListener('click', function() {
Twinkle.config.listDialog.reset($prefbutton, dlgtbody);
}, false);
button.textContent = 'بازنشانی'; // localized
dialogcontent.appendChild(button);
button = document.createElement('button');
button.setAttribute('type', 'submit'); // so Morebits.simpleWindow puts the button in the button pane
button.addEventListener('click', function() {
dialog.close(); // the event parameter on this function seems to be broken
}, false);
button.textContent = 'لغو'; // localized
dialogcontent.appendChild(button);
dialog.setContent(dialogcontent);
dialog.display();
};
// Resets the data value, re-populates based on the new (default) value, then saves the
// old data value again (less surprising behaviour)
Twinkle.config.listDialog.reset = function twinkleconfigListDialogReset(button, tbody) {
// reset value on button
var $button = $(button);
var curpref = $button.data('pref');
var oldvalue = $button.data('value');
Twinkle.config.resetPref(curpref);
// reset form
var $tbody = $(tbody);
$tbody.find('tr').slice(1).remove(); // all rows except the first (header) row
// add the new values
var curvalue = $button.data('value');
$.each(curvalue, function(k, v) {
Twinkle.config.listDialog.addRow(tbody, v.value, v.label);
});
// save the old value
$button.data('value', oldvalue);
};
Twinkle.config.listDialog.save = function twinkleconfigListDialogSave(button, tbody) {
var result = [];
var current = {};
$(tbody).find('input[type="text"]').each(function(inputkey, input) {
if ($(input).hasClass('twinkle-config-customlist-value')) {
current = { value: input.value };
} else {
current.label = input.value;
// exclude totally empty rows
if (current.value || current.label) {
result.push(current);
}
}
});
$(button).data('value', result);
};
// reset/restore defaults
Twinkle.config.resetPrefLink = function twinkleconfigResetPrefLink(e) {
var wantedpref = e.target.id.substring(21); // "twinkle-config-reset-" prefix is stripped
// search tactics
$(Twinkle.config.sections).each(function(sectionkey, section) {
if (section.hidden || (section.adminOnly && !Morebits.userIsSysop)) {
return true; // continue: skip impossibilities
}
var foundit = false;
$(section.preferences).each(function(prefkey, pref) {
if (pref.name !== wantedpref) {
return true; // continue
}
Twinkle.config.resetPref(pref);
foundit = true;
return false; // break
});
if (foundit) {
return false; // break
}
});
return false; // stop link from scrolling page
};
Twinkle.config.resetPref = function twinkleconfigResetPref(pref) {
switch (pref.type) {
case 'boolean':
document.getElementById(pref.name).checked = Twinkle.defaultConfig[pref.name];
break;
case 'string':
case 'integer':
case 'enum':
document.getElementById(pref.name).value = Twinkle.defaultConfig[pref.name];
break;
case 'set':
$.each(pref.setValues, function(itemkey) {
if (document.getElementById(pref.name + '_' + itemkey)) {
document.getElementById(pref.name + '_' + itemkey).checked = Twinkle.defaultConfig[pref.name].indexOf(itemkey) !== -1;
}
});
break;
case 'customList':
$(document.getElementById(pref.name)).data('value', Twinkle.defaultConfig[pref.name]);
break;
default:
alert('تنظیمات توینکل: نوع داده برای ' + pref.name + ' اشتباه است'); // localized
break;
}
};
Twinkle.config.resetAllPrefs = function twinkleconfigResetAllPrefs() {
// no confirmation message - the user can just refresh/close the page to abort
$(Twinkle.config.sections).each(function(sectionkey, section) {
if (section.hidden || (section.adminOnly && !Morebits.userIsSysop)) {
return true; // continue: skip impossibilities
}
$(section.preferences).each(function(prefkey, pref) {
if (!pref.adminOnly || Morebits.userIsSysop) {
Twinkle.config.resetPref(pref);
}
});
return true;
});
return false; // stop link from scrolling page
};
Twinkle.config.save = function twinkleconfigSave(e) {
Morebits.status.init(document.getElementById('twinkle-config-content'));
var userjs = mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').user] + ':' + mw.config.get('wgUserName') + '/twinkleoptions.js';
var wikipedia_page = new Morebits.wiki.page(userjs, 'ذخیره تنظیمات در ' + userjs); // localized
wikipedia_page.setCallbackParameters(e.target);
wikipedia_page.load(Twinkle.config.writePrefs);
return false;
};
Twinkle.config.writePrefs = function twinkleconfigWritePrefs(pageobj) {
var form = pageobj.getCallbackParameters();
// this is the object which gets serialized into JSON; only
// preferences that this script knows about are kept
var newConfig = {optionsVersion: 2};
// a comparison function is needed later on
// it is just enough for our purposes (i.e. comparing strings, numbers, booleans,
// arrays of strings, and arrays of { value, label })
// and it is not very robust: e.g. compare([2], ["2"]) === true, and
// compare({}, {}) === false, but it's good enough for our purposes here
var compare = function(a, b) {
if (Array.isArray(a)) {
if (a.length !== b.length) {
return false;
}
var asort = a.sort(), bsort = b.sort();
for (var i = 0; asort[i]; ++i) {
// comparison of the two properties of custom lists
if ((typeof asort[i] === 'object') && (asort[i].label !== bsort[i].label ||
asort[i].value !== bsort[i].value)) {
return false;
} else if (asort[i].toString() !== bsort[i].toString()) {
return false;
}
}
return true;
}
return a === b;
};
$(Twinkle.config.sections).each(function(sectionkey, section) {
if (section.adminOnly && !Morebits.userIsSysop) {
return; // i.e. "continue" in this context
}
// reach each of the preferences from the form
$(section.preferences).each(function(prefkey, pref) {
var userValue; // = undefined
// only read form values for those prefs that have them
if (!pref.adminOnly || Morebits.userIsSysop) {
if (!section.hidden) {
switch (pref.type) {
case 'boolean': // read from the checkbox
userValue = form[pref.name].checked;
break;
case 'string': // read from the input box or combo box
case 'enum':
userValue = form[pref.name].value;
break;
case 'integer': // read from the input box
userValue = parseInt(form[pref.name].value, 10);
if (isNaN(userValue)) {
Morebits.status.warn('در حال ذخیره', 'مقداری که برای ' + pref.name + ' (' + pref.value + ') انتخاب کردهاید اشتباه است. ذخیره کردن ادامه مییابد اما مقدار مشکلدار ثبت نمیشود.'); // localized
userValue = null;
}
break;
case 'set': // read from the set of check boxes
userValue = [];
if (pref.setDisplayOrder) {
// read only those keys specified in the display order
$.each(pref.setDisplayOrder, function(itemkey, item) {
if (form[pref.name + '_' + item].checked) {
userValue.push(item);
}
});
} else {
// read all the keys in the list of values
$.each(pref.setValues, function(itemkey) {
if (form[pref.name + '_' + itemkey].checked) {
userValue.push(itemkey);
}
});
}
break;
case 'customList': // read from the jQuery data stored on the button object
userValue = $(form[pref.name]).data('value');
break;
default:
alert('twinkleconfig: unknown data type for preference ' + pref.name);
break;
}
} else if (Twinkle.prefs) {
// Retain the hidden preferences that may have customised by the user from twinkleoptions.js
// undefined if not set
userValue = Twinkle.prefs[pref.name];
}
}
// only save those preferences that are *different* from the default
if (userValue !== undefined && !compare(userValue, Twinkle.defaultConfig[pref.name])) {
newConfig[pref.name] = userValue;
}
});
});
var text =
'// twinkleoptions.js: personal Twinkle preferences file\n' +
'//\n' +
'// NOTE: The easiest way to change your Twinkle preferences is by using the\n' +
'// Twinkle preferences panel, at [[' + Morebits.pageNameNorm + ']].\n' +
'//\n' +
'// This file is AUTOMATICALLY GENERATED. Any changes you make (aside from\n' +
'// changing the configuration parameters in a valid-JavaScript way) will be\n' +
'// overwritten the next time you click "save" in the Twinkle preferences\n' +
'// panel. If modifying this file, make sure to use correct JavaScript.\n' +
'// <no' + 'wiki>\n' +
'\n' +
'window.Twinkle.prefs = ';
text += JSON.stringify(newConfig, null, 2);
text +=
';\n' +
'\n' +
'// </no' + 'wiki>\n' +
'// End of twinkleoptions.js\n';
pageobj.setPageText(text);
pageobj.setEditSummary('ذخیره تنظیمات توینکل: ویرایش خودکار از [[:' + Morebits.pageNameNorm + ']] ([[وو:تل]])'); // localized
pageobj.setCreateOption('recreate');
pageobj.save(Twinkle.config.saveSuccess);
};
Twinkle.config.saveSuccess = function twinkleconfigSaveSuccess(pageobj) {
pageobj.getStatusElement().info('successful');
var noticebox = document.createElement('div');
noticebox.className = 'successbox';
noticebox.style.fontSize = '100%';
noticebox.style.marginTop = '2em';
noticebox.innerHTML = '<p><b>تنظیمات توینکل شما ذخیره شد.</b></p><p>برای دیدن تغییرات باید <b>کاشه مرورگر را خالی کنید</b> (توضیح بیشتر در<a href="' + mw.util.getUrl('WP:BYPASS') + '" title="وو:کاشه">وو:کاشه</a>).</p>'; // localized
Morebits.status.root.appendChild(noticebox);
var noticeclear = document.createElement('br');
noticeclear.style.clear = 'both';
Morebits.status.root.appendChild(noticeclear);
};
})(jQuery);
// </nowiki>
/* ===== twinklewarn.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklewarn.js: Warn module
****************************************
* Mode of invocation: Tab ("Warn")
* Active on: Any page with relevant user name (userspace, contribs,
* etc.), as well as the rollback success page
*/
Twinkle.warn = function twinklewarn() {
if (mw.config.get('wgRelevantUserName')) {
Twinkle.addPortletLink(Twinkle.warn.callback, 'هشدار', 'tw-warn', 'هشدار/اعلان به کاربر'); // localized
if (Twinkle.getPref('autoMenuAfterRollback') &&
mw.config.get('wgNamespaceNumber') === 3 &&
mw.util.getParamValue('vanarticle') &&
!mw.util.getParamValue('friendlywelcome') &&
!mw.util.getParamValue('noautowarn')) {
Twinkle.warn.callback();
}
}
// Modify URL of talk page on rollback success pages, makes use of a
// custom message box in [[MediaWiki:Rollback-success]]
if (mw.config.get('wgAction') === 'rollback') {
var $vandalTalkLink = $('#mw-rollback-success').find('.mw-usertoollinks a').first();
if ($vandalTalkLink.length) {
$vandalTalkLink.css('font-weight', 'bold');
$vandalTalkLink.wrapInner($('<span/>').attr('title', 'در صورت صلاحدید، میتوانید از توینکل برای هشدار به کاربر دربارهٔ ویرایشهایش در این صفحه استفاده کنید.')); // localized
// Can't provide vanarticlerevid as only wgCurRevisionId is provided
var extraParam = 'vanarticle=' + mw.util.rawurlencode(Morebits.pageNameNorm);
var href = $vandalTalkLink.attr('href');
if (href.indexOf('?') === -1) {
$vandalTalkLink.attr('href', href + '?' + extraParam);
} else {
$vandalTalkLink.attr('href', href + '&' + extraParam);
}
}
}
};
// Used to close window when switching to ARV in autolevel
Twinkle.warn.dialog = null;
Twinkle.warn.callback = function twinklewarnCallback() {
if (mw.config.get('wgRelevantUserName') === mw.config.get('wgUserName') &&
!confirm('شما در حال هشدار به خودتان هستید! آیا از انجام این کار مطمئن هستید؟')) { // localized
return;
}
var dialog;
Twinkle.warn.dialog = new Morebits.simpleWindow(600, 440);
dialog = Twinkle.warn.dialog;
dialog.setTitle('هشدار/اعلان به کاربر'); // localized
dialog.setScriptName('توینکل'); // localized
dialog.addFooterLink('انتخاب سطح هشدار', 'en:WP:UWUL#Levels'); // localized
dialog.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#warn'); // localized
var form = new Morebits.quickForm(Twinkle.warn.callback.evaluate);
var main_select = form.append({
type: 'field',
label: 'نوع هشدار/اعلان برای این مشکل را انتخاب کنید', // localized
tooltip: 'ابتدا گروه اصلی هشدار، و سپس یک هشدار اختصاصی برای این مشکل را انتحاب کنید.' // localized
});
var main_group = main_select.append({
type: 'select',
name: 'main_group',
event: Twinkle.warn.callback.change_category
});
var defaultGroup = parseInt(Twinkle.getPref('defaultWarningGroup'), 10);
main_group.append({ type: 'option', label: 'هشدار خرابکاری (نخستین هشدار)', value: 'singlenotice', selected: defaultGroup !== 7 }); // localized
main_group.append({ type: 'option', label: 'آخرین هشدار خرابکاری', value: 'singlewarn', selected: defaultGroup === 7 }); // localized
main_select.append({ type: 'select', name: 'sub_group', event: Twinkle.warn.callback.change_subcategory }); // Will be empty to begin with.
form.append({
type: 'input',
name: 'article',
label: 'صفحهٔ پیوند دادهشده', // localized
value: mw.util.getParamValue('vanarticle') || '',
tooltip: 'میتوان یک صفحه را از درون اعلان پیوند داد، شاید به این دلیل که این اعلان بهواسطهٔ یک خنثیسازی در صفحهٔ مذکور ارسال شدهاست. برای اینکه پیوندی داده نشود، این جعبه را خالی بگذارید.' // localized
});
form.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-warn-revert-messages'
});
var vanrevid = mw.util.getParamValue('vanarticlerevid');
if (vanrevid) {
var message = '';
var query = {};
// If you tried reverting, check if *you* actually reverted
if (!mw.util.getParamValue('noautowarn') && mw.util.getParamValue('vanarticle')) { // Via fluff link
query = {
action: 'query',
titles: mw.util.getParamValue('vanarticle'),
prop: 'revisions',
rvstartid: vanrevid,
rvlimit: 2,
rvdir: 'newer',
rvprop: 'user'
};
new Morebits.wiki.api('بررسی این که واگردانی شما در این صفحه موفق بود یا نه', query, function(apiobj) { // localized
var revertUser = $(apiobj.getResponse()).find('revisions rev')[1].getAttribute('user');
if (revertUser && revertUser !== mw.config.get('wgUserName')) {
message += ' کس دیگری این صفحه را واگردانی کردهاست و ممکن است به کاربر نیز هشدار دادهباشد.'; // localized
$('#twinkle-warn-revert-messages').text('Note:' + message);
}
}).post();
}
// Confirm edit wasn't too old for a warning
query = {
action: 'query',
prop: 'revisions',
rvprop: 'timestamp',
revids: vanrevid
};
new Morebits.wiki.api('گرفتن برچسب زمان نسخهها', query, function(apiobj) { // localized
var vantimestamp = $(apiobj.getResponse()).find('revisions rev').attr('timestamp');
var revDate = new Morebits.date(vantimestamp);
if (vantimestamp && revDate.isValid()) {
if (revDate.add(24, 'hours').isBefore(new Date())) {
message += ' این ویرایش بیشتر از ۲۴ ساعت پیش انجام شدهاست و ممکن است نیازی به هشدار نباشد.'; // localized
$('#twinkle-warn-revert-messages').text('Note:' + message);
}
}
}).post();
}
var more = form.append({ type: 'field', name: 'reasonGroup', label: 'اطلاعات هشدار' }); // localized
more.append({ type: 'textarea', label: 'پیام اختیاری:', name: 'reason', tooltip: 'شاید لازم باشد یک دلیل یا یک اعلان با جزئیات بیشتر اضافه شود' }); // localized
var previewlink = document.createElement('a');
$(previewlink).click(function() {
Twinkle.warn.callbacks.preview(result); // |result| is defined below
});
previewlink.style.cursor = 'pointer';
previewlink.textContent = 'پیشنمایش'; // localized
more.append({ type: 'div', id: 'warningpreview', label: [ previewlink ] });
more.append({ type: 'div', id: 'twinklewarn-previewbox', style: 'display: none' });
more.append({ type: 'submit', label: 'ثبت' }); // localized
var result = form.render();
dialog.setContent(result);
dialog.display();
result.main_group.root = result;
result.previewer = new Morebits.wiki.preview($(result).find('div#twinklewarn-previewbox').last()[0]);
// We must init the first choice (General Note);
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.main_group.dispatchEvent(evt);
};
// This is all the messages that might be dispatched by the code
// Each of the individual templates require the following information:
// label (required): A short description displayed in the dialog
// summary (required): The edit summary used. If an article name is entered, the summary is postfixed with "on [[article]]", and it is always postfixed with ". $summaryAd"
// suppressArticleInSummary (optional): Set to true to suppress showing the article name in the edit summary. Useful if the warning relates to attack pages, or some such.
Twinkle.warn.messages = {
levels: {},
singlenotice: {
'هشدار خرابکاری': {
label: 'هشدار خرابکاری — نخستین هشدار به خرابکار',
summary: 'هشدار خرابکاری'
}
},
singlewarn: {
'آخرین هشدار خرابکاری': {
label: 'آخرین هشدار خرابکاری',
summary: 'آخرین هشدار خرابکاری'
}
}
};
Twinkle.warn.talkpageObj = null;
Twinkle.warn.callback.change_category = function twinklewarnCallbackChangeCategory(e) {
var value = e.target.value;
var sub_group = e.target.root.sub_group;
sub_group.main_group = value;
var old_subvalue = sub_group.value;
var old_subvalue_re;
if (old_subvalue) {
if (value === 'kitchensink') { // Exact match possible in kitchensink menu
old_subvalue_re = new RegExp(mw.util.escapeRegExp(old_subvalue));
} else {
old_subvalue = old_subvalue.replace(/\d*(im)?$/, '');
old_subvalue_re = new RegExp(mw.util.escapeRegExp(old_subvalue) + '(\\d*(?:im)?)$');
}
}
while (sub_group.hasChildNodes()) {
sub_group.removeChild(sub_group.firstChild);
}
var selected = false;
// worker function to create the combo box entries
var createEntries = function(contents, container, wrapInOptgroup, val) {
val = typeof val !== 'undefined' ? val : value; // IE doesn't support default parameters
// level2->2, singlewarn->''; also used to distinguish the
// scaled levels from singlenotice, singlewarn, and custom
var level = val.replace(/^\D+/g, '');
// due to an apparent iOS bug, we have to add an option-group to prevent truncation of text
// (search WT:TW archives for "Problem selecting warnings on an iPhone")
if (wrapInOptgroup && $.client.profile().platform === 'iphone') {
var wrapperOptgroup = new Morebits.quickForm.element({
type: 'optgroup',
label: 'الگوهای موجود' // localized
});
wrapperOptgroup = wrapperOptgroup.render();
container.appendChild(wrapperOptgroup);
container = wrapperOptgroup;
}
$.each(contents, function(itemKey, itemProperties) {
// Skip if the current template doesn't have a version for the current level
if (!!level && !itemProperties[val]) {
return;
}
var key = typeof itemKey === 'string' ? itemKey : itemProperties.value;
var template = key + level;
var elem = new Morebits.quickForm.element({
type: 'option',
label: '{{' + template + '}}: ' + (level ? itemProperties[val].label : itemProperties.label),
value: template
});
// Select item best corresponding to previous selection
if (!selected && old_subvalue && old_subvalue_re.test(template)) {
elem.data.selected = selected = true;
}
var elemRendered = container.appendChild(elem.render());
$(elemRendered).data('messageData', itemProperties);
});
};
switch (value) {
case 'singlenotice':
case 'singlewarn':
createEntries(Twinkle.warn.messages[value], sub_group, true);
break;
case 'singlecombined':
var unSortedSinglets = $.extend({}, Twinkle.warn.messages.singlenotice, Twinkle.warn.messages.singlewarn);
var sortedSingletMessages = {};
Object.keys(unSortedSinglets).sort().forEach(function(key) {
sortedSingletMessages[key] = unSortedSinglets[key];
});
createEntries(sortedSingletMessages, sub_group, true);
break;
case 'custom':
createEntries(Twinkle.getPref('customWarningList'), sub_group, true);
break;
case 'kitchensink':
['level1', 'level2', 'level3', 'level4', 'level4im'].forEach(function(lvl) {
$.each(Twinkle.warn.messages.levels, function(_, levelGroup) {
createEntries(levelGroup, sub_group, true, lvl);
});
});
createEntries(Twinkle.warn.messages.singlenotice, sub_group, true);
createEntries(Twinkle.warn.messages.singlewarn, sub_group, true);
createEntries(Twinkle.getPref('customWarningList'), sub_group, true);
break;
case 'level1':
case 'level2':
case 'level3':
case 'level4':
case 'level4im':
// Creates subgroup regardless of whether there is anything to place in it;
// leaves "Removal of deletion tags" empty for 4im
$.each(Twinkle.warn.messages.levels, function(groupLabel, groupContents) {
var optgroup = new Morebits.quickForm.element({
type: 'optgroup',
label: groupLabel
});
optgroup = optgroup.render();
sub_group.appendChild(optgroup);
// create the options
createEntries(groupContents, optgroup, false);
});
break;
case 'autolevel':
// Check user page to determine appropriate level
var autolevelProc = function() {
var wikitext = Twinkle.warn.talkpageObj.getPageText();
// history not needed for autolevel
var latest = Twinkle.warn.callbacks.dateProcessing(wikitext)[0];
// Pseudo-params with only what's needed to parse the level i.e. no messageData
var params = {
sub_group: old_subvalue,
article: e.target.root.article.value
};
var lvl = 'level' + Twinkle.warn.callbacks.autolevelParseWikitext(wikitext, params, latest)[1];
// Identical to level1, etc. above but explicitly provides the level
$.each(Twinkle.warn.messages.levels, function(groupLabel, groupContents) {
var optgroup = new Morebits.quickForm.element({
type: 'optgroup',
label: groupLabel
});
optgroup = optgroup.render();
sub_group.appendChild(optgroup);
// create the options
createEntries(groupContents, optgroup, false, lvl);
});
// Trigger subcategory change, add select menu, etc.
Twinkle.warn.callback.postCategoryCleanup(e);
};
if (Twinkle.warn.talkpageObj) {
autolevelProc();
} else {
var usertalk_page = new Morebits.wiki.page('بحث کاربر:' + mw.config.get('wgRelevantUserName'), 'بارگیری هشدارهای قبلی'); // localized
usertalk_page.setFollowRedirect(true);
usertalk_page.load(function(pageobj) {
Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj
autolevelProc();
});
}
break;
default:
alert('Unknown warning group in twinklewarn');
break;
}
// Trigger subcategory change, add select menu, etc.
// Here because of the async load for autolevel
if (value !== 'autolevel') {
// reset any autolevel-specific messages while we're here
$('#twinkle-warn-autolevel-message').remove();
Twinkle.warn.callback.postCategoryCleanup(e);
}
};
Twinkle.warn.callback.postCategoryCleanup = function twinklewarnCallbackPostCategoryCleanup(e) {
// clear overridden label on article textbox
Morebits.quickForm.setElementTooltipVisibility(e.target.root.article, true);
Morebits.quickForm.resetElementLabel(e.target.root.article);
// Trigger custom label/change on main category change
Twinkle.warn.callback.change_subcategory(e);
// Use select2 to make the select menu searchable
if (!Twinkle.getPref('oldSelect')) {
$('select[name=sub_group]')
.select2({
width: '100%',
matcher: Morebits.select2.matchers.optgroupFull,
templateResult: Morebits.select2.highlightSearchMatches,
language: {
searching: Morebits.select2.queryInterceptor
}
})
.change(Twinkle.warn.callback.change_subcategory);
$('.select2-selection').keydown(Morebits.select2.autoStart);
mw.util.addCSS(
// prevent dropdown from appearing behind the dialog, just in case
'.select2-container { z-index: 10000; }' +
// Increase height
'.select2-container .select2-dropdown .select2-results > .select2-results__options { max-height: 350px; }' +
// Reduce padding
'.select2-results .select2-results__option { padding-top: 1px; padding-bottom: 1px; }' +
'.select2-results .select2-results__group { padding-top: 1px; padding-bottom: 1px; } ' +
// Adjust font size
'.select2-container .select2-dropdown .select2-results { font-size: 13px; }' +
'.select2-container .selection .select2-selection__rendered { font-size: 13px; }'
);
}
};
Twinkle.warn.callback.change_subcategory = function twinklewarnCallbackChangeSubcategory(e) {
var main_group = e.target.form.main_group.value;
var value = e.target.form.sub_group.value;
// Tags that don't take a linked article, but something else (often a username).
// The value of each tag is the label next to the input field
var notLinkedArticle = {
'uw-agf-sock': 'اختیاری: نام کاربری حساب دیگر (بدون پیشوند کاربر:) ', // localized
'uw-bite': "نام کاربری کاربر «چماقخورده» (بدون پیشوند کاربر:) ", // localized
'uw-socksuspect': 'نام کاربری حساب اصلی، در صورت اطلاع (بدون پیشوند کاربر:) ', // localized
'uw-username': 'نام کاربری ناقض سیاست است زیرا... ', // localized
'uw-aiv': 'اختیاری: نام کاربری گزارششده (بدون پیشوند کاربر:) ' // localized
};
if (['singlenotice', 'singlewarn', 'singlecombined', 'kitchensink'].indexOf(main_group) !== -1) {
if (notLinkedArticle[value]) {
if (Twinkle.warn.prev_article === null) {
Twinkle.warn.prev_article = e.target.form.article.value;
}
e.target.form.article.notArticle = true;
e.target.form.article.value = '';
// change form labels according to the warning selected
Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, false);
Morebits.quickForm.overrideElementLabel(e.target.form.article, notLinkedArticle[value]);
} else if (e.target.form.article.notArticle) {
if (Twinkle.warn.prev_article !== null) {
e.target.form.article.value = Twinkle.warn.prev_article;
Twinkle.warn.prev_article = null;
}
e.target.form.article.notArticle = false;
Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, true);
Morebits.quickForm.resetElementLabel(e.target.form.article);
}
}
// add big red notice, warning users about how to use {{uw-[coi-]username}} appropriately
$('#tw-warn-red-notice').remove();
var $redWarning;
if (value === 'uw-username') {
$redWarning = $("<div style='color: red;' id='tw-warn-red-notice'>{{نام کاربری نامناسب}} <b>نباید</b> برای موارد نقض <b>آشکار</b> سیاستها استفاده شود. " + // localized
"نقضهای آشکار باید مستقیماً در [[وو:تامنام]] گزارش شوند (با استفاده از زبانهٔ بررسی و گزارش پیشرفتهٔ توینکل). " + // localized
'{{نام کاربری نامناسب}} باید تنها در موارد لبمرزی و جهت ورود به گفتگو با کاربر استفاده شود.</div>'); // localized
$redWarning.insertAfter(Morebits.quickForm.getElementLabelObject(e.target.form.reasonGroup));
} else if (value === 'uw-coi-username') {
$redWarning = $("<div style='color: red;' id='tw-warn-red-notice'>{{uw-coi-username}} <b>نباید</b> برای موارد نقض <b>آشکار</b>سیاست نام کاربری استفاده شود. " + // localized
"نقضهای آشکار باید مستقیماً در [[وو:تامنام]] گزارش شوند (با استفاده از زبانهٔ بررسی و گزارش پیشرفتهٔ توینکل). " + // localized
'{{uw-coi-username}} باید تنها در موارد لبمرزی و جهت ورود به گفتگو با کاربر استفاده شود.</div>'); // localized
$redWarning.insertAfter(Morebits.quickForm.getElementLabelObject(e.target.form.reasonGroup));
}
};
Twinkle.warn.callbacks = {
getWarningWikitext: function(templateName, article, reason, isCustom) {
var text = '{{subst:' + templateName;
// add linked article for user warnings
if (article) {
// c&pmove has the source as the first parameter
if (templateName === 'uw-c&pmove') {
text += '|to=' + article;
} else {
text += '|1=' + article;
}
}
if (reason && !isCustom) {
// add extra message
if (templateName === 'uw-csd' || templateName === 'uw-probation' ||
templateName === 'uw-userspacenoindex' || templateName === 'uw-userpage') {
text += "|3=''" + reason + "''";
} else {
text += "|2=''" + reason + "''";
}
}
text += '}}';
if (reason && isCustom) {
// we assume that custom warnings lack a {{{2}}} parameter
text += " ''" + reason + "''";
}
return text + ' ~~~~';
},
showPreview: function(form, templatename) {
// Provided on autolevel, not otherwise
templatename = templatename || form.sub_group.value;
var linkedarticle = form.article.value;
var templatetext;
templatetext = Twinkle.warn.callbacks.getWarningWikitext(templatename, linkedarticle,
form.reason.value, form.main_group.value === 'custom');
form.previewer.beginRender(templatetext, 'User_talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username
},
// Just a pass-through unless the autolevel option was selected
preview: function(form) {
if (form.main_group.value === 'autolevel') {
// Always get a new, updated talkpage for autolevel processing
var usertalk_page = new Morebits.wiki.page('بحث کاربر:' + mw.config.get('wgRelevantUserName'), 'بارگیری هشدارهای قبلی'); // localized
usertalk_page.setFollowRedirect(true);
usertalk_page.load(function(pageobj) {
Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj
var wikitext = pageobj.getPageText();
// history not needed for autolevel
var latest = Twinkle.warn.callbacks.dateProcessing(wikitext)[0];
var params = {
sub_group: form.sub_group.value,
article: form.article.value,
messageData: $(form.sub_group).find('option[value="' + $(form.sub_group).val() + '"]').data('messageData')
};
var template = Twinkle.warn.callbacks.autolevelParseWikitext(wikitext, params, latest)[0];
Twinkle.warn.callbacks.showPreview(form, template);
// If the templates have diverged, fake a change event
// to reload the menu with the updated pageobj
if (form.sub_group.value !== template) {
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
form.main_group.dispatchEvent(evt);
}
});
} else {
Twinkle.warn.callbacks.showPreview(form);
}
},
/**
* Used in the main and autolevel loops to determine when to warn
* about excessively recent, stale, or identical warnings.
* @param {string} wikitext The text of a user's talk page, from getPageText()
* @returns {Object[]} - Array of objects: latest contains most recent
* warning and date; history lists all prior warnings
*/
dateProcessing: function(wikitext) {
var history_re = /<!--\s?Template:([uU]w-.*?)\s?-->.*?(\d{1,2}:\d{1,2}, \d{1,2} \w+ \d{4} \(UTC\))/g;
var history = {};
var latest = { date: new Morebits.date(0), type: '' };
var current;
while ((current = history_re.exec(wikitext)) !== null) {
var template = current[1], current_date = new Morebits.date(current[2]);
if (!(template in history) || history[template].isBefore(current_date)) {
history[template] = current_date;
}
if (!latest.date.isAfter(current_date)) {
latest.date = current_date;
latest.type = template;
}
}
return [latest, history];
},
/**
* Main loop for deciding what the level should increment to. Most of
* this is really just error catching and updating the subsequent data.
* May produce up to two notices in a twinkle-warn-autolevel-messages div
*
* @param {string} wikitext The text of a user's talk page, from getPageText() (required)
* @param {Object} params Params object: sub_group is the template (required);
* article is the user-provided article (form.article) used to link ARV on recent level4 warnings;
* messageData is only necessary if getting the full template, as it's
* used to ensure a valid template of that level exists
* @param {Object} latest First element of the array returned from
* dateProcessing. Provided here rather than processed within to avoid
* repeated call to dateProcessing
* @param {(Date|Morebits.date)} date Date from which staleness is determined
* @param {Morebits.status} statelem Status element, only used for handling error in final execution
*
* @returns {Array} - Array that contains the full template and just the warning level
*/
autolevelParseWikitext: function(wikitext, params, latest, date, statelem) {
var template = params.sub_group.replace(/(.*)\d$/, '$1');
var level; // undefined rather than '' means the isNaN below will return true
if (/\d(?:im)?$/.test(latest.type)) { // level1-4im
level = parseInt(latest.type.replace(/.*(\d)(?:im)?$/, '$1'), 10);
} else if (latest.type) { // Non-numbered warning
// Try to leverage existing categorization of
// warnings, all but one are universally lowercased
var loweredType = /uw-multipleIPs/i.test(template) ? 'uw-multipleIPs' : template.toLowerCase();
// It would be nice to account for blocks, but in most
// cases the hidden message is terminal, not the sig
if (Twinkle.warn.messages.singlewarn[loweredType]) {
level = 3;
} else {
level = 1; // singlenotice or not found
}
}
var $autolevelMessage = $('<div/>', {'id': 'twinkle-warn-autolevel-message'});
if (isNaN(level)) { // No prior warnings found, this is the first
level = 1;
} else if (level > 4 || level < 1) { // Shouldn't happen
var message = 'Unable to parse previous warning level, please manually select a warning level.';
if (statelem) {
statelem.error(message);
} else {
alert(message);
}
return;
} else {
date = date || new Date();
var autoTimeout = new Morebits.date(latest.date.getTime()).add(parseInt(Twinkle.getPref('autolevelStaleDays'), 10), 'days');
if (autoTimeout.isAfter(date)) {
if (level === 4) {
level = 4;
// Basically indicates whether we're in the final Main evaluation or not,
// and thus whether we can continue or need to display the warning and link
if (!statelem) {
var $link = $('<a/>', {
'href': '#',
'text': 'برای باز کردن ابزار بررسی و هشدار پیشرفته اینجا کلیک کنید.', // localized
'css': { 'fontWeight': 'bold' },
'click': function() {
Morebits.wiki.actionCompleted.redirect = null;
Twinkle.warn.dialog.close();
Twinkle.arv.callback(mw.config.get('wgRelevantUserName'));
$('input[name=page]').val(params.article); // Target page
$('input[value=final]').prop('checked', true); // Vandalism after final
}
});
var statusNode = $('<div/>', {
'text': mw.config.get('wgRelevantUserName') + ' اخیراً یک هشدار سطح ۴ دریافت کردهاست (' + latest.type + ') بنابراین احتمالاً گزارش کاربر گزینهٔ بهتری است; ', // localized
'css': {'color': 'red' }
});
statusNode.append($link[0]);
$autolevelMessage.append(statusNode);
}
} else { // Automatically increase severity
level += 1;
}
} else { // Reset warning level if most-recent warning is too old
level = 1;
}
}
// Validate warning level, falling back to the uw-generic series.
// Only a few items are missing a level, and in all but a handful
// of cases, the uw-generic series is explicitly used elsewhere per WP:UTM.
if (params.messageData && !params.messageData['level' + level]) {
template = 'uw-generic';
}
template += level;
$autolevelMessage.prepend($('<div>Will issue a <span style="font-weight: bold;">level ' + level + '</span> template.</div>'));
// After the only other message: the (text-only) staleness note
$('#twinkle-warn-autolevel-message').remove(); // clean slate
$autolevelMessage.insertAfter($('#twinkle-warn-warning-message'));
return [template, level];
},
main: function(pageobj) {
var text = pageobj.getPageText();
var statelem = pageobj.getStatusElement();
var params = pageobj.getCallbackParameters();
var messageData = params.messageData;
// JS somehow didn't get destructured assignment until ES6 so of course IE doesn't support it
var warningHistory = Twinkle.warn.callbacks.dateProcessing(text);
var latest = warningHistory[0];
var history = warningHistory[1];
var now = new Morebits.date(pageobj.getLoadTime());
Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj, just in case
if (params.main_group === 'autolevel') {
// [template, level]
var templateAndLevel = Twinkle.warn.callbacks.autolevelParseWikitext(text, params, latest, now, statelem);
// Only if there's a change from the prior display/load
if (params.sub_group !== templateAndLevel[0] && !confirm('Will issue a {{' + templateAndLevel[0] + '}} template to the user, okay?')) {
statelem.error('aborted per user request');
return;
}
// Update params now that we've selected a warning
params.sub_group = templateAndLevel[0];
messageData = params.messageData['level' + templateAndLevel[1]];
} else if (params.sub_group in history) {
if (new Morebits.date(history[params.sub_group]).add(1, 'day').isAfter(now)) {
if (!confirm('یک ' + params.sub_group + ' همسان در ۲۴ ساعت گذشته صادر شدهاست. \nهمچنان مایل هستید این هشدار/اعلان را بیافزایید؟')) { // localized
statelem.error('به درخواست کاربر لغو شد'); // localized
return;
}
}
}
latest.date.add(1, 'minute'); // after long debate, one minute is max
if (latest.date.isAfter(now)) {
if (!confirm('A ' + latest.type + ' در یک دقیقهٔ گذشته صادر شدهاست. \nهمچنان مایل هستید این هشدار/اعلان را بیافزایید؟')) { // localized
statelem.error('به درخواست کاربر لغو شد'); // localized
return;
}
}
var dateHeaderRegex = now.monthHeaderRegex(), dateHeaderRegexLast, dateHeaderRegexResult;
while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) {
dateHeaderRegexResult = dateHeaderRegexLast;
}
// If dateHeaderRegexResult is null then lastHeaderIndex is never checked. If it is not null but
// \n== is not found, then the date header must be at the very start of the page. lastIndexOf
// returns -1 in this case, so lastHeaderIndex gets set to 0 as desired.
var lastHeaderIndex = text.lastIndexOf('\n==') + 1;
if (text.length > 0) {
text += '\n\n';
}
if (messageData.heading) {
text += '== ' + messageData.heading + ' ==\n';
} else if (!dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex) {
Morebits.status.info('Info', 'یک سرتیتر سطح ۲ برای تاریخ خواهد ساخت، زیرا هیچ سرتیتری برای این ماه یافت نشد'); // localized
text += now.monthHeader() + '\n';
}
text += Twinkle.warn.callbacks.getWarningWikitext(params.sub_group, params.article,
params.reason, params.main_group === 'custom');
if (Twinkle.getPref('showSharedIPNotice') && mw.util.isIPAddress(mw.config.get('wgTitle'))) {
Morebits.status.info('Info', 'افزودن اعلان آیپی مشترک'); // localized
text += '\n{{subst:آگاهی آیپی مشترک}}'; // localized
}
// build the edit summary
var summary;
if (params.main_group === 'custom') {
switch (params.sub_group.substr(-1)) {
case '1':
summary = 'یادآوری کلی'; // localized
break;
case '2':
summary = 'اخطار'; // localized
break;
case '3':
summary = 'هشدار'; // localized
break;
case '4':
summary = 'آخرین هشدار'; // localized
break;
case 'm':
if (params.sub_group.substr(-3) === '4im') {
summary = 'تنها هشدار'; // localized
break;
}
summary = 'اعلان'; // localized
break;
default:
summary = 'اعلان'; // localized
break;
}
summary += ': ' + Morebits.string.toUpperCaseFirstChar(messageData.label);
} else {
// Normalize kitchensink to the 1-4im style
if (params.main_group === 'kitchensink' && !/^D+$/.test(params.sub_group)) {
var sub = params.sub_group.substr(-1);
if (sub === 'm') {
sub = params.sub_group.substr(-3);
}
// Don't overwrite uw-3rr, technically unnecessary
if (/\d/.test(sub)) {
params.main_group = 'level' + sub;
}
}
summary = /^\D+$/.test(params.main_group) ? messageData.summary : messageData[params.main_group].summary;
if (messageData.suppressArticleInSummary !== true && params.article) {
if (params.sub_group === 'uw-agf-sock' ||
params.sub_group === 'uw-socksuspect' ||
params.sub_group === 'uw-aiv') { // these templates require a username
summary += ' of [[:کاربر:' + params.article + ']]'; // localized
} else {
summary += ' on [[:' + params.article + ']]';
}
}
}
summary += '.' + Twinkle.getPref('summaryAd');
pageobj.setPageText(text);
pageobj.setEditSummary(summary);
pageobj.setWatchlist(Twinkle.getPref('watchWarnings'));
pageobj.save();
}
};
Twinkle.warn.callback.evaluate = function twinklewarnCallbackEvaluate(e) {
var userTalkPage = 'بحث_کاربر:' + mw.config.get('wgRelevantUserName');
// First, check to make sure a reason was filled in if uw-username was selected
if (e.target.sub_group.value === 'uw-username' && e.target.article.value.trim() === '') {
alert('لازم است که برای الگوی {{نام کاربری نامناسب}} یک دلیل ارائه کنید.'); // localized
return;
}
// Find the selected <option> element so we can fetch the data structure
var selectedEl = $(e.target.sub_group).find('option[value="' + $(e.target.sub_group).val() + '"]');
// Then, grab all the values provided by the form
var params = {
reason: e.target.reason.value,
main_group: e.target.main_group.value,
sub_group: e.target.sub_group.value,
article: e.target.article.value, // .replace( /^(Image|Category):/i, ':$1:' ), -- apparently no longer needed...
messageData: selectedEl.data('messageData')
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(e.target);
Morebits.wiki.actionCompleted.redirect = userTalkPage;
Morebits.wiki.actionCompleted.notice = 'هشدار دادن کامل شد، بارگیری صفحهٔ بحث تا چند ثانیهٔ دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'User talk page modification');
wikipedia_page.setCallbackParameters(params);
wikipedia_page.setFollowRedirect(true);
wikipedia_page.load(Twinkle.warn.callbacks.main);
};
})(jQuery);
// </nowiki>
/* ===== twinklearv.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklearv.js: ARV module
****************************************
* Mode of invocation: Tab ("ARV")
* Active on: Any page with relevant user name (userspace, contribs, etc.)
*
* Localized for fa.wiktionary (ویکیواژه):
* - The only report venue is the admin noticeboard
* «ویکیواژه:تابلوی اعلانات مدیران», with the subpage
* «ویکیواژه:تابلوی اعلانات مدیران/نامهای کاربری نامناسب» for usernames.
* - SPI (sock/puppet) and AN3 (edit-warring) venues do not exist here and were removed.
*/
// Venue pages on fa.wiktionary
var ADMIN_NOTICEBOARD = 'ویکیواژه:تابلوی اعلانات مدیران';
var USERNAME_NOTICEBOARD = 'ویکیواژه:تابلوی اعلانات مدیران/نامهای کاربری نامناسب';
Twinkle.arv = function twinklearv() {
var username = mw.config.get('wgRelevantUserName');
if (!username || username === mw.config.get('wgUserName')) {
return;
}
var isIP = mw.util.isIPAddress(username);
var title = isIP ? 'گزارش نشانی آیپی به مدیران' : 'گزارش کاربر به مدیران';
Twinkle.addPortletLink(function() {
Twinkle.arv.callback(username, isIP);
}, 'ARV', 'tw-arv', title);
};
Twinkle.arv.callback = function (uid, isIP) {
var Window = new Morebits.simpleWindow(600, 500);
Window.setTitle('گزارش کاربر به مدیران');
Window.setScriptName('Twinkle');
Window.addFooterLink('تابلوی اعلانات مدیران', ADMIN_NOTICEBOARD);
var form = new Morebits.quickForm(Twinkle.arv.callback.evaluate);
var categories = form.append({
type: 'select',
name: 'category',
label: 'نوع گزارش را انتخاب کنید: ',
event: Twinkle.arv.callback.changeCategory
});
categories.append({
type: 'option',
label: 'خرابکاری / گزارش عمومی',
value: 'aiv'
});
categories.append({
type: 'option',
label: 'نام کاربری نامناسب',
value: 'username',
disabled: mw.util.isIPAddress(uid)
});
form.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-arv-blockwarning'
});
form.append({
type: 'field',
label: 'محل کار',
name: 'work_area'
});
form.append({ type: 'submit' });
form.append({
type: 'hidden',
name: 'uid',
value: uid
});
var result = form.render();
Window.setContent(result);
Window.display();
// Check if the user is blocked, update notice
var query = {
action: 'query',
list: 'blocks',
bkprop: 'range|flags',
format: 'json'
};
if (isIP) {
query.bkip = uid;
} else {
query.bkusers = uid;
}
new Morebits.wiki.api('بررسی وضعیت قطع دسترسی کاربر', query, function(apiobj) {
var blocklist = apiobj.getResponse().query.blocks;
if (blocklist.length) {
var block = blocklist[0];
var message = (isIP ? 'این نشانی آیپی' : 'این حساب') + ' هماکنون ' + (block.partial ? 'بهطور جزئی ' : '') + 'قطع دسترسی شده است';
// Start and end differ, range blocked
message += block.rangestart !== block.rangeend ? ' (بهعنوان بخشی از قطع دسترسی محدوده).' : '.';
if (block.partial) {
$('#twinkle-arv-blockwarning').css('color', 'black'); // Less severe
}
$('#twinkle-arv-blockwarning').text(message);
}
}).post();
// We must init the
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.category.dispatchEvent(evt);
};
Twinkle.arv.callback.changeCategory = function (e) {
var value = e.target.value;
var root = e.target.form;
var old_area = Morebits.quickForm.getElements(root, 'work_area')[0];
var work_area = null;
switch (value) {
case 'aiv':
/* falls through */
default:
work_area = new Morebits.quickForm.element({
type: 'field',
label: 'گزارش کاربر برای خرابکاری',
name: 'work_area'
});
work_area.append({
type: 'input',
name: 'page',
label: 'صفحهٔ مرتبط: ',
tooltip: 'برای پیوند ندادن به صفحه در گزارش، خالی بگذارید',
value: mw.util.getParamValue('vanarticle') || '',
event: function(e) {
var value = e.target.value;
var root = e.target.form;
if (value === '') {
root.badid.disabled = root.goodid.disabled = true;
} else {
root.badid.disabled = false;
root.goodid.disabled = root.badid.value === '';
}
}
});
work_area.append({
type: 'input',
name: 'badid',
label: 'شناسهٔ نسخهٔ صفحهٔ هدف هنگام خرابکاری: ',
tooltip: 'برای نبود پیوند تفاوت، خالی بگذارید',
value: mw.util.getParamValue('vanarticlerevid') || '',
disabled: !mw.util.getParamValue('vanarticle'),
event: function(e) {
var value = e.target.value;
var root = e.target.form;
root.goodid.disabled = value === '';
}
});
work_area.append({
type: 'input',
name: 'goodid',
label: 'شناسهٔ آخرین نسخهٔ سالم پیش از خرابکاری صفحهٔ هدف: ',
tooltip: 'برای پیوند تفاوت با نسخهٔ پیشین، خالی بگذارید',
value: mw.util.getParamValue('vanarticlegoodrevid') || '',
disabled: !mw.util.getParamValue('vanarticle') || mw.util.getParamValue('vanarticlerevid')
});
work_area.append({
type: 'checkbox',
name: 'arvtype',
list: [
{
label: 'خرابکاری پس از آخرین اخطار (سطح ۴ یا ۴im)',
value: 'final'
},
{
label: 'خرابکاری در کمتر از یک روز پس از پایان قطع دسترسی',
value: 'postblock'
},
{
label: 'آشکارا یک حساب صرفاً خرابکاری',
value: 'vandalonly',
disabled: mw.util.isIPAddress(root.uid.value)
},
{
label: 'حساب صرفاً برای تبلیغ',
value: 'promoonly',
disabled: mw.util.isIPAddress(root.uid.value)
},
{
label: 'حساب آشکارا یک ربات هرزنامه یا حساب هکشده است',
value: 'spambot'
}
]
});
work_area.append({
type: 'textarea',
name: 'reason',
label: 'توضیح: '
});
work_area = work_area.render();
old_area.parentNode.replaceChild(work_area, old_area);
break;
case 'username':
work_area = new Morebits.quickForm.element({
type: 'field',
label: 'گزارش نام کاربری نامناسب',
name: 'work_area'
});
work_area.append({
type: 'header',
label: 'نوع(های) نام کاربری نامناسب'
});
work_area.append({
type: 'checkbox',
name: 'arvtype',
list: [
{
label: 'نام کاربری گمراهکننده',
value: 'misleading',
tooltip: 'نامهای کاربری گمراهکننده تصور نادرستی دربارهٔ مشارکتکننده ایجاد میکنند؛ برای نمونه تصور اقتدار نابجا یا تصور حساب ربات.'
},
{
label: 'نام کاربری تبلیغاتی',
value: 'promotional',
tooltip: 'نامهای کاربری تبلیغاتی تبلیغ یک شرکت، وبگاه یا گروه هستند.'
},
{
label: 'نام کاربری که استفادهٔ اشتراکی را میرساند',
value: 'shared',
tooltip: 'نامهای کاربری که احتمال استفادهٔ اشتراکی را میرسانند (نام شرکتها یا گروهها) مجاز نیستند.'
},
{
label: 'نام کاربری توهینآمیز',
value: 'offensive',
tooltip: 'نامهای کاربری توهینآمیز ویرایش هماهنگ را دشوار یا ناممکن میکنند.'
},
{
label: 'نام کاربری مخل',
value: 'disruptive',
tooltip: 'نامهای کاربری مخل شامل حملههای شخصی یا نشاندهندهٔ قصد آشکار برای اخلال هستند.'
}
]
});
work_area.append({
type: 'textarea',
name: 'reason',
label: 'توضیح:'
});
work_area = work_area.render();
old_area.parentNode.replaceChild(work_area, old_area);
break;
}
};
Twinkle.arv.callback.evaluate = function(e) {
var form = e.target;
var reason = '';
var comment = '';
if (form.reason) {
comment = form.reason.value;
}
var uid = form.uid.value;
var types;
switch (form.category.value) {
// Report user for vandalism / general report
case 'aiv':
/* falls through */
default:
types = form.getChecked('arvtype');
if (!types.length && comment === '') {
alert('باید دلیلی مشخص کنید');
return;
}
types = types.map(function(v) {
switch (v) {
case 'final':
return 'خرابکاری پس از آخرین اخطار';
case 'postblock':
return 'خرابکاری پس از پایان اخیر قطع دسترسی';
case 'vandalonly':
return 'رفتار آشکارا نشاندهندهٔ حساب صرفاً خرابکاری است';
case 'promoonly':
return 'حساب تنها برای مقاصد تبلیغاتی استفاده میشود';
case 'spambot':
return 'حساب آشکارا یک ربات هرزنامه یا حساب هکشده است';
default:
return 'دلیل نامشخص';
}
}).join('؛ ');
if (form.page.value !== '') {
reason = 'در [[:' + form.page.value + ']]';
if (form.badid.value !== '') {
reason += ' ([[Special:Diff/' + form.goodid.value + '/' + form.badid.value + '|تفاوت]])';
}
reason += ':';
}
if (types) {
reason += ' ' + types;
}
if (comment !== '') {
reason += (reason === '' ? '' : '. ') + comment;
}
reason = reason.trim();
if (!/[.؟!؛]$/.test(reason)) {
reason += '.';
}
reason += ' ~~' + '~~';
reason = reason.replace(/\r?\n/g, '\n*:'); // indent newlines
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = ADMIN_NOTICEBOARD;
Morebits.wiki.actionCompleted.notice = 'گزارش انجام شد';
var aivPage = new Morebits.wiki.page(ADMIN_NOTICEBOARD, 'در حال پردازش گزارش');
aivPage.setFollowRedirect(true);
aivPage.load(function() {
var text = aivPage.getPageText();
// check if user has already been reported
if (text.indexOf('[[Special:Contributions/' + uid + '|') !== -1) {
aivPage.getStatusElement().error('این کاربر پیشتر گزارش شده است؛ گزارش تازهای افزوده نمیشود');
Morebits.status.printUserText(reason, 'متنی که نوشتید در زیر آمده است، در صورت تمایل میتوانید آن را بهصورت دستی زیر گزارش موجود بگذارید:');
return;
}
aivPage.getStatusElement().status('در حال افزودن گزارش تازه...');
aivPage.setEditSummary('گزارش [[Special:Contributions/' + uid + '|' + uid + ']].');
aivPage.setChangeTags(Twinkle.changeTags);
aivPage.setAppendText('\n* [[Special:Contributions/' + uid + '|' + uid + ']] – ' + reason);
aivPage.append();
});
break;
// Report inappropriate username
case 'username':
types = form.getChecked('arvtype').map(function(v) {
switch (v) {
case 'misleading':
return 'گمراهکننده';
case 'promotional':
return 'تبلیغاتی';
case 'shared':
return 'اشتراکی';
case 'offensive':
return 'توهینآمیز';
case 'disruptive':
return 'مخل';
default:
return v;
}
});
reason = '* [[Special:Contributions/' + uid + '|' + uid + ']] – ';
if (types.length) {
reason += 'نقض سیاست نام کاربری؛ نام کاربری ' + types.join('، ') + '. ';
}
if (comment !== '') {
reason += comment + '. ';
}
reason += '~~' + '~~';
reason = reason.replace(/\r?\n/g, '\n*:'); // indent newlines
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = USERNAME_NOTICEBOARD;
Morebits.wiki.actionCompleted.notice = 'گزارش انجام شد';
var uaaPage = new Morebits.wiki.page(USERNAME_NOTICEBOARD, 'در حال پردازش گزارش نام کاربری');
uaaPage.setFollowRedirect(true);
uaaPage.load(function() {
var text = uaaPage.getPageText();
// check if user has already been reported
if (text.indexOf('[[Special:Contributions/' + uid + '|') !== -1) {
uaaPage.getStatusElement().error('این کاربر پیشتر فهرست شده است.');
Morebits.status.printUserText(reason, 'متنی که نوشتید در زیر آمده است، در صورت تمایل میتوانید آن را بهصورت دستی زیر گزارش موجود بگذارید:');
return;
}
uaaPage.getStatusElement().status('در حال افزودن گزارش تازه...');
uaaPage.setEditSummary('گزارش [[Special:Contributions/' + uid + '|' + uid + ']].');
uaaPage.setChangeTags(Twinkle.changeTags);
uaaPage.setPageText(text + '\n' + reason);
uaaPage.save();
});
break;
}
};
Twinkle.addInitCallback(Twinkle.arv, 'arv');
})(jQuery);
// </nowiki>
/* ===== twinkleblock.js ===== */
// <nowiki>
(function($) {
var api = new mw.Api(), relevantUserName;
var menuFormattedNamespaces = $.extend({}, mw.config.get('wgFormattedNamespaces'));
menuFormattedNamespaces[0] = '(مقاله)'; // localized
/*
****************************************
*** twinkleblock.js: Block module
****************************************
* Mode of invocation: Tab ("Block")
* Active on: Any page with relevant user name (userspace, contribs, etc.)
*/
Twinkle.block = function twinkleblock() {
// should show on Contributions or Block pages, anywhere there's a relevant user
if (Morebits.userIsSysop && mw.config.get('wgRelevantUserName')) {
Twinkle.addPortletLink(Twinkle.block.callback, 'بستن', 'tw-block', 'قطع دسترسی یک کاربر'); // localized
}
};
Twinkle.block.callback = function twinkleblockCallback() {
if (mw.config.get('wgRelevantUserName') === mw.config.get('wgUserName') &&
!confirm('شما در حال قطع دسترسی خودتان هستید! آیا مطمئنید که میخواهید ادامه دهید؟')) { // localized
return;
}
Twinkle.block.currentBlockInfo = undefined;
Twinkle.block.field_block_options = {};
Twinkle.block.field_template_options = {};
var Window = new Morebits.simpleWindow(650, 530);
// need to be verbose about who we're blocking
Window.setTitle('قطع دسترسی یا افزودن الگوی قطع دسترسی برای ' + mw.config.get('wgRelevantUserName')); // localized
Window.setScriptName('توینکل'); // localized
// Window.addFooterLink('Block templates', 'Template:Uw-block/doc/Block_templates'); // localized [removed]
Window.addFooterLink('سیاست قطع دسترسی', 'وو:بستن'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#block'); // localized
var form = new Morebits.quickForm(Twinkle.block.callback.evaluate);
var actionfield = form.append({
type: 'field',
label: 'نوع عمل' // localized
});
actionfield.append({
type: 'checkbox',
name: 'actiontype',
event: Twinkle.block.callback.change_action,
list: [
{
label: 'قطع دسترسی کاربر', // localized
value: 'قطع دسترسی', // localized
tooltip: 'با استفاده از گزینههای ارائه شده، دسترسی کاربر را قطع کنید. اگر قطع دسترسی موردی انتخاب نشود، قطع دسترسی کلی خواهد بود.', // localized
checked: true
},
{
label: 'قطع دسترسی موردی', // localized
value: 'partial',
tooltip: 'انجام یا ثبت قطع دسترسی موردی', // localized
checked: Twinkle.getPref('defaultToPartialBlocks')
},
{
label: 'افزودن الگوی قطع دسترسی به صفحهٔ بحث کاربر', // localized
value: 'template',
tooltip: 'اگر مدیری که قطع دسترسی را انجام داده فراموش کرده که الگوی قطع دسترسی را در صفحهٔ بحث کاربر بگذارد، یا شما حسابی را قطع دسترسی کردید بدون آن که الگوی مذکور را در صفحهٔ بحث کاربر بگذارید، میتوانید از این روش برای ثبت الگوی مناسب استفاده کنید.', // localized
checked: true
}
]
});
form.append({ type: 'field', label: 'تنظیم اولیه', name: 'field_preset' }); // localized
form.append({ type: 'field', label: 'گزینههای الگو', name: 'field_template_options' }); // localized
form.append({ type: 'field', label: 'گزینههای بستن', name: 'field_block_options' }); // localized
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
result.root = result;
Twinkle.block.fetchUserInfo(function() {
// clean up preset data (defaults, etc.), done exactly once, must be before Twinkle.block.callback.change_action is called
Twinkle.block.transformBlockPresets();
if (Twinkle.block.currentBlockInfo) {
Window.addFooterLink('باز کردن کاربر', 'Special:Unblock/' + mw.config.get('wgRelevantUserName'), true); // localized
}
// init the controls after user and block info have been fetched
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.actiontype[0].dispatchEvent(evt);
});
};
Twinkle.block.fetchUserInfo = function twinkleblockFetchUserInfo(fn) {
api.get({
format: 'json',
action: 'query',
list: 'blocks|users|logevents',
letype: 'block',
lelimit: 1,
bkusers: mw.config.get('wgRelevantUserName'),
ususers: mw.config.get('wgRelevantUserName'),
letitle: 'User:' + mw.config.get('wgRelevantUserName')
})
.then(function(data) {
var blockinfo = data.query.blocks[0],
userinfo = data.query.users[0];
Twinkle.block.isRegistered = !!userinfo.userid;
relevantUserName = Twinkle.block.isRegistered ? 'User:' + mw.config.get('wgRelevantUserName') : mw.config.get('wgRelevantUserName');
if (blockinfo) {
// handle frustrating system of inverted boolean values
blockinfo.disabletalk = blockinfo.allowusertalk === undefined;
blockinfo.hardblock = blockinfo.anononly === undefined;
Twinkle.block.currentBlockInfo = blockinfo;
}
Twinkle.block.hasBlockLog = !!data.query.logevents.length;
// Used later to check if block status changed while filling out the form
Twinkle.block.blockLogId = Twinkle.block.hasBlockLog ? data.query.logevents[0].logid : false;
if (typeof fn === 'function') {
return fn();
}
}, function(msg) {
Morebits.status.init($('div[name="currentblock"] span').last()[0]);
Morebits.status.warn('خطا در دریافت اطلاعات کاربر', msg); // localized
});
};
Twinkle.block.callback.saveFieldset = function twinkleblockCallbacksaveFieldset(fieldset) {
Twinkle.block[$(fieldset).prop('name')] = {};
$(fieldset).serializeArray().forEach(function(el) {
// namespaces and pages for partial blocks are overwritten
// here, but we're handling them elsewhere so that's fine
Twinkle.block[$(fieldset).prop('name')][el.name] = el.value;
});
};
Twinkle.block.callback.change_action = function twinkleblockCallbackChangeAction(e) {
var field_preset, field_template_options, field_block_options, $form = $(e.target.form);
// Make ifs shorter
var blockBox = $form.find('[name=actiontype][value=block]').is(':checked');
var templateBox = $form.find('[name=actiontype][value=template]').is(':checked');
var partial = $form.find('[name=actiontype][value=partial]');
var partialBox = partial.is(':checked');
var blockGroup = partialBox ? Twinkle.block.blockGroupsPartial : Twinkle.block.blockGroups;
partial.prop('disabled', !blockBox && !templateBox);
Twinkle.block.callback.saveFieldset($('[name=field_block_options]'));
Twinkle.block.callback.saveFieldset($('[name=field_template_options]'));
if (blockBox) {
field_preset = new Morebits.quickForm.element({ type: 'field', label: 'Preset', name: 'field_preset' });
field_preset.append({
type: 'select',
name: 'preset',
label: 'انتخاب یک تنظیم اولیه:', // localized
event: Twinkle.block.callback.change_preset,
list: Twinkle.block.callback.filtered_block_groups(blockGroup)
});
field_block_options = new Morebits.quickForm.element({ type: 'field', label: 'گزینههای قطع دسترسی', name: 'field_block_options' }); // localized
field_block_options.append({ type: 'div', name: 'hasblocklog', label: ' ' });
field_block_options.append({ type: 'div', name: 'currentblock', label: ' ' });
field_block_options.append({
type: 'select',
name: 'expiry_preset',
label: 'زمان سرآمدن:', // localized
event: Twinkle.block.callback.change_expiry,
list: [
{ label: 'سفارشی', value: 'custom', selected: true }, // localized
{ label: 'بیپایان', value: 'infinity' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۲۴ ساعت', value: '24 hours' }, // localized
{ label: '۳۱ ساعت', value: '31 hours' }, // localized
{ label: '۳۶ ساعت', value: '36 hours' }, // localized
{ label: '۴۸ ساعت', value: '48 hours' }, // localized
{ label: '۶۰ ساعت', value: '60 hours' }, // localized
{ label: '۷۲ ساعت', value: '72 hours' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۶ ماه', value: '6 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: '۲ سال', value: '2 years' }, // localized
{ label: '۳ سال', value: '3 years' } // localized
]
});
field_block_options.append({
type: 'input',
name: 'expiry',
label: 'زمان سرآمدن سفارشی', // localized
tooltip: 'میتوانید یک زمان نسبی به انگلیسی وارد کنید (مثلاً 1 minute یا 19 days) یا این که یک زمان مشخص را به صورت yyyymmddhhmm با ارقام انگلسی وارد کنید (مثلاً 200602011405 میشود ساعت ۱۴:۰۵ روز ۱ فوریهٔ ۲۰۰۶)', // localizde
value: Twinkle.block.field_block_options.expiry || Twinkle.block.field_template_options.template_expiry
});
if (partialBox) { // Partial block
field_block_options.append({
type: 'select',
multiple: true,
name: 'pagerestrictions',
label: 'صفحههای مورد قطع دسترسی', // localized
value: '',
tooltip: 'حداکثر ۱۰ مورد.' // localized
});
var ns = field_block_options.append({
type: 'select',
multiple: true,
name: 'namespacerestrictions',
label: 'فضاهای نام مورد قطع دسترسی', // localized
value: '',
tooltip: 'جلوگیری از ویرایش این فضاهای نام.', // localized
});
$.each(menuFormattedNamespaces, function(number, name) {
// Ignore -1: Special; -2: Media; and 2300-2303: Gadget (talk) and Gadget definition (talk)
if (number >= 0 && number < 830) {
ns.append({ type: 'option', label: name, value: number });
}
});
}
var blockoptions = [
{
checked: Twinkle.block.field_block_options.nocreate,
label: 'جلوگیری از ایجاد حساب', // localized
name: 'nocreate',
value: '1'
},
{
checked: Twinkle.block.field_block_options.noemail,
label: 'جلوگیری از ارسال ایمیل', // localized
name: 'noemail',
value: '1'
},
{
checked: Twinkle.block.field_block_options.disabletalk,
label: 'جلوگیری از ویرایش صفحهٔ بحث در مدت قطع دسترسی', // localized
name: 'disabletalk',
value: '1',
tooltip: partialBox ? 'در صورت اعمال قطع دسترسی موردی، این گزینه باید خاموش باشد مگر اینکه شما بخواهید از ویرایش کاربر در فضای نام بحث کاربر نیز جلوگیری کنید' : '' // localized
}
];
if (Twinkle.block.isRegistered) {
blockoptions.push({
checked: Twinkle.block.field_block_options.autoblock,
label: 'بستن خودکار نشانیهایآیپی', // localized
name: 'autoblock',
value: '1'
});
} else {
blockoptions.push({
checked: Twinkle.block.field_block_options.hardblock,
label: 'بستن حسابهای ثبتنام کرده از همین نشانی آیپی', // localized
name: 'hardblock',
value: '1'
});
}
blockoptions.push({
checked: Twinkle.block.field_block_options.watchuser,
label: 'بستن صفحههای کاربر و بحث کاربر', // localized
name: 'watchuser',
value: '1'
});
field_block_options.append({
type: 'checkbox',
name: 'blockoptions',
list: blockoptions
});
field_block_options.append({
type: 'textarea',
label: 'دلیل (برای سیاههٔ قطع دسترسی):', // localized
name: 'reason',
tooltip: 'لطفاً جزئیات مفیدی را به پیام پیشفرض اضافه کنید.', // localized
value: Twinkle.block.field_block_options.reason
});
/*
field_block_options.append({
type: 'div',
name: 'filerlog_label',
label: 'See also:',
style: 'display:inline-block;font-style:normal !important',
tooltip: 'Insert a "see also" message to indicate whether the filter log or deleted contributions played a role in the decision to block.'
});
field_block_options.append({
type: 'checkbox',
name: 'filter_see_also',
event: Twinkle.block.callback.toggle_see_alsos,
style: 'display:inline-block; margin-right:5px',
list: [
{
label: 'Filter log',
checked: false,
value: 'filter log'
}
]
});
field_block_options.append({
type: 'checkbox',
name: 'deleted_see_also',
event: Twinkle.block.callback.toggle_see_alsos,
style: 'display:inline-block',
list: [
{
label: 'Deleted contribs',
checked: false,
value: 'deleted contribs'
}
]
});
*/ // localized [removed]
if (Twinkle.block.currentBlockInfo) {
field_block_options.append({ type: 'hidden', name: 'reblock', value: '1' });
}
}
if (templateBox) {
field_template_options = new Morebits.quickForm.element({ type: 'field', label: 'گزینههای الگو', name: 'field_template_options' }); // localized
field_template_options.append({
type: 'select',
name: 'template',
label: 'Choose talk page template:',
event: Twinkle.block.callback.change_template,
list: Twinkle.block.callback.filtered_block_groups(blockGroup, true),
value: Twinkle.block.field_template_options.template
});
field_template_options.append({
type: 'input',
name: 'article',
display: 'none',
label: 'Linked page',
value: '',
tooltip: 'A page can be linked within the notice, perhaps if it was the primary target of disruption. Leave empty for no page to be linked.'
});
// Only visible if partial and not blocking
field_template_options.append({
type: 'input',
name: 'area',
display: 'none',
label: 'Area blocked from',
value: '',
tooltip: 'توضیح اختیاری صفحات یا مکانهای نام کاربر از ویرایش مسدود شده است.' // localized
});
if (!blockBox) {
field_template_options.append({
type: 'input',
name: 'template_expiry',
display: 'none',
label: 'Period of blocking: ',
value: '',
tooltip: 'مدت زمان مسدود شدن برای نمونه ، ۲۴ ساعت ، ۲ هفته ، نامشخص و غیره ...' // localized
});
}
field_template_options.append({
type: 'input',
name: 'block_reason',
label: '"You have been blocked for ..." ',
display: 'none',
tooltip: 'یک دلیل اختیاری برای جایگزینی دلیل عمومی پیش فرض. فقط برای الگوهای بستن عمومی موجود است.', // localized
value: Twinkle.block.field_template_options.block_reason
});
if (blockBox) {
field_template_options.append({
type: 'checkbox',
name: 'blank_duration',
list: [
{
label: 'قرار ندادن تاریخ انقضا در الگو', // localized
checked: Twinkle.block.field_template_options.blank_duration,
tooltip: 'به جای اینکه مدت زمان را اضافه کنید، الگوی بستن را بخوانید "شما به طور موقت مسدود شدهاید ..."' // localized
}
]
});
} else {
field_template_options.append({
type: 'checkbox',
list: [
{
label: 'جلوگیری از ویرایش صفحهٔ بحث', // localized
name: 'notalk',
checked: Twinkle.block.field_template_options.notalk,
tooltip: 'Make the block template state that the user\'s talk page access has been removed'
},
{
label: 'جلوگیری از ارسال ایمیل', // localized
name: 'noemail_template',
checked: Twinkle.block.field_template_options.noemail_template,
tooltip: 'If the area is not provided, make the block template state that the user\'s email access has been removed'
},
{
label: 'جلوگیری از ایجاد حساب کاربری', // localized
name: 'nocreate_template',
checked: Twinkle.block.field_template_options.nocreate_template,
tooltip: 'If the area is not provided, make the block template state that the user\'s ability to create accounts has been removed'
}
]
});
}
var $previewlink = $('<a id="twinkleblock-preivew-link">Preview</a>');
$previewlink.off('click').on('click', function() {
Twinkle.block.callback.preview($form[0]);
});
$previewlink.css({cursor: 'pointer'});
field_template_options.append({ type: 'div', id: 'blockpreview', label: [ $previewlink[0] ] });
field_template_options.append({ type: 'div', id: 'twinkleblock-previewbox', style: 'display: none' });
}
var oldfield;
if (field_preset) {
oldfield = $form.find('fieldset[name="field_preset"]')[0];
oldfield.parentNode.replaceChild(field_preset.render(), oldfield);
} else {
$form.find('fieldset[name="field_preset"]').hide();
}
if (field_block_options) {
oldfield = $form.find('fieldset[name="field_block_options"]')[0];
oldfield.parentNode.replaceChild(field_block_options.render(), oldfield);
$form.find('[name=pagerestrictions]').select2({
width: '100%',
placeholder: 'صفحات را برای بستن کاربر انتخاب کنید', // localized
language: {
errorLoading: function() {
return 'Incomplete or invalid search term';
}
},
maximumSelectionLength: 10, // Software limitation [[phab:T202776]]
minimumInputLength: 1, // prevent ajax call when empty
ajax: {
url: mw.util.wikiScript('api'),
dataType: 'json',
delay: 100,
data: function(params) {
var title = mw.Title.newFromText(params.term);
if (!title) {
return;
}
return {
'action': 'query',
'format': 'json',
'list': 'allpages',
'apfrom': title.title,
'apnamespace': title.namespace,
'aplimit': '10'
};
},
processResults: function(data) {
return {
results: data.query.allpages.map(function(page) {
var title = mw.Title.newFromText(page.title, page.ns).toText();
return {
id: title,
text: title
};
})
};
}
},
templateSelection: function(choice) {
return $('<a>').text(choice.text).attr({
href: mw.util.getUrl(choice.text),
target: '_blank'
});
}
});
$form.find('[name=namespacerestrictions]').select2({
width: '100%',
matcher: Morebits.select2.matchers.wordBeginning,
language: {
searching: Morebits.select2.queryInterceptor
},
templateResult: Morebits.select2.highlightSearchMatches,
placeholder: 'Select namespaces to block user from'
});
mw.util.addCSS(
// prevent dropdown from appearing behind the dialog, just in case
'.select2-container { z-index: 10000; }' +
// Reduce padding
'.select2-results .select2-results__option { padding-top: 1px; padding-bottom: 1px; }' +
// Adjust font size
'.select2-container .select2-dropdown .select2-results { font-size: 13px; }' +
'.select2-container .selection .select2-selection__rendered { font-size: 13px; }' +
// Remove black border
'.select2-container--default.select2-container--focus .select2-selection--multiple { border: 1px solid #aaa; }' +
// Make the tiny cross larger
'.select2-selection__choice__remove { font-size: 130%; }'
);
} else {
$form.find('fieldset[name="field_block_options"]').hide();
// Clear select2 options
$form.find('[name=pagerestrictions]').val(null).trigger('change');
$form.find('[name=namespacerestrictions]').val(null).trigger('change');
}
if (field_template_options) {
oldfield = $form.find('fieldset[name="field_template_options"]')[0];
oldfield.parentNode.replaceChild(field_template_options.render(), oldfield);
e.target.form.root.previewer = new Morebits.wiki.preview($(e.target.form.root).find('#twinkleblock-previewbox').last()[0]);
} else {
$form.find('fieldset[name="field_template_options"]').hide();
}
if (Twinkle.block.hasBlockLog) {
var $blockloglink = $('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: mw.config.get('wgRelevantUserName'), type: 'block'}) + '">block log</a>)');
Morebits.status.init($('div[name="hasblocklog"] span').last()[0]);
Morebits.status.warn('This user has been blocked in the past', $blockloglink[0]);
}
if (Twinkle.block.currentBlockInfo) {
Morebits.status.init($('div[name="currentblock"] span').last()[0]);
// list=blocks without bkprops (as we do in fetchUerInfo)
// returns partial: '' if the user is partially blocked
var statusStr = relevantUserName + ' is ' + (Twinkle.block.currentBlockInfo.partial === '' ? 'partially blocked' : 'blocked sitewide');
if (Twinkle.block.currentBlockInfo.expiry === 'infinity') {
statusStr += ' (indef)';
} else if (new Morebits.date(Twinkle.block.currentBlockInfo.expiry).isValid()) {
statusStr += ' (expires ' + new Morebits.date(Twinkle.block.currentBlockInfo.expiry).calendar('utc') + ')';
}
var infoStr = 'برای تغییر مسدود شدن سوال کنید'; // localized
if (Twinkle.block.currentBlockInfo.partial === undefined && partialBox) {
infoStr += ', converting to a partial block';
} else if (Twinkle.block.currentBlockInfo.partial === '' && !partialBox) {
infoStr += ', converting to a sitewide block';
}
Morebits.status.warn(statusStr, infoStr);
Twinkle.block.callback.update_form(e, Twinkle.block.currentBlockInfo);
}
if (templateBox) {
// make sure all the fields are correct based on defaults
if (blockBox) {
Twinkle.block.callback.change_preset(e);
} else {
Twinkle.block.callback.change_template(e);
}
}
};
/*
* Keep alphabetized by key name, Twinkle.block.blockGroups establishes
* the order they will appear in the interface
*
* Block preset format, all keys accept only 'true' (omit for false) except where noted:
* <title of block template> : {
* autoblock: <autoblock any IP addresses used (for registered users only)>
* disabletalk: <disable user from editing their own talk page while blocked>
* expiry: <string - expiry timestamp, can include relative times like "5 months", "2 weeks" etc>
* forAnonOnly: <show block option in the interface only if the relevant user is an IP>
* forRegisteredOnly: <show block option in the interface only if the relevant user is registered>
* label: <string - label for the option of the dropdown in the interface (keep brief)>
* noemail: prevent the user from sending email through Special:Emailuser
* pageParam: <set if the associated block template accepts a page parameter>
* prependReason: <string - prepends the value of 'reason' to the end of the existing reason, namely for when revoking talk page access>
* nocreate: <block account creation from the user's IP (for anonymous users only)>
* nonstandard: <template does not conform to stewardship of WikiProject User Warnings and may not accept standard parameters>
* reason: <string - block rationale, as would appear in the block log,
* and the edit summary for when adding block template, unless 'summary' is set>
* reasonParam: <set if the associated block template accepts a reason parameter>
* sig: <string - set to ~~~~ if block template does not accept "true" as the value, or set null to omit sig param altogether>
* summary: <string - edit summary for when adding block template to user's talk page, if not set, 'reason' is used>
* suppressArticleInSummary: <set to suppress showing the article name in the edit summary, as with attack pages>
* templateName: <string - name of template to use (instead of key name), entry will be omitted from the Templates list.
* (e.g. use another template but with different block options)>
* useInitialOptions: <when preset is chosen, only change given block options, leave others as they were>
*
* WARNING: 'anononly' and 'allowusertalk' are enabled by default.
* To disable, set 'hardblock' and 'disabletalk', respectively
*/
Twinkle.block.blockPresetsInfo = {
'anonblock': {
expiry: '31 hours',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{anonblock}}',
sig: '~~~~'
},
'anonblock - school': {
expiry: '36 hours',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{anonblock}} <!-- Likely a school based on behavioral evidence -->',
templateName: 'anonblock',
sig: '~~~~'
},
'blocked proxy': {
expiry: '1 year',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
hardblock: true,
reason: '{{پروکسی باز}}', // localized
sig: null
},
'CheckUser block': {
expiry: '1 week',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{قطع دسترسی بازرسی کاربر}}', // localized
sig: '~~~~'
},
'checkuserblock-account': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{بندایش بازرسی کاربر-حساب کاربری}}', // localized
sig: '~~~~'
},
'checkuserblock-wide': {
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{checkuserblock-wide}}',
sig: '~~~~'
},
'colocationwebhost': {
expiry: '1 year',
forAnonOnly: true,
nonstandard: true,
reason: '{{میزبانیوبسرور}}', // localized
sig: null
},
'oversightblock': {
autoblock: true,
expiry: 'infinity',
nocreate: true,
nonstandard: true,
reason: '{{OversightBlock}}',
sig: '~~~~'
},
'school block': {
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{school block}}',
sig: '~~~~'
},
'spamblacklistblock': {
forAnonOnly: true,
expiry: '1 month',
disabletalk: true,
nocreate: true,
reason: '{{spamblacklistblock}} <!-- editor only attempts to add blacklisted links, see [[Special:Log/spamblacklist]] -->'
},
// Placeholder for when we add support for rangeblocks
// 'rangeblock' : {
// reason: '{{rangeblock}}',
// nocreate: true,
// nonstandard: true,
// forAnonOnly: true,
// sig: '~~~~'
// },
'tor': {
expiry: '1 year',
forAnonOnly: true,
nonstandard: true,
reason: '{{Tor}}',
sig: null
},
'webhostblock': {
expiry: '1 year',
forAnonOnly: true,
nonstandard: true,
reason: '{{میزبان وب بستهشده}}', // localized
sig: null
},
// uw-prefixed
'uw-3block': {
autoblock: true,
expiry: '24 hours',
nocreate: true,
pageParam: true,
reason: 'نقض[[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]]', // localized
summary: 'برای نقض [[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]] شما از ویرایش منع شدهاید' // localized
},
'uw-ablock': {
autoblock: true,
expiry: '31 hours',
forAnonOnly: true,
nocreate: true,
pageParam: true,
reasonParam: true,
summary: 'آدرس IP شما از ویرایش منع شده است', // localized
suppressArticleInSummary: true
},
'uw-adblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: 'استفاده از ویکیواژه به عنوان [[ویکیواژه:هرزنامه|هرزنامه]] یا [[ویکیواژه:ویکیواژه چه چیزی نیست|تبلیغات]] ', // localized
summary: 'شما به دلیل [[ویکیواژه:ویکیواژه چه چیزی نیست|ویکیواژه چه چیزی نیست]] از ویرایش منع شدهاید' // localized
},
'uw-aeblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: '[[WP:Arbitration enforcement|Arbitration enforcement]]',
reasonParam: true,
summary: 'شما به دلیل نقض [[WP:Arbitration|تصمیم داوری]] از ویرایش منع شده اید' // localized
},
'uw-bioblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: 'نقض سیایت شرح حال [[ویکیواژه:زندگینامه زندگان|زندگینامه زندگان]] ', // localized
summary: 'You have been blocked from editing for violations of Wikipedia\'s [[ویکیواژه:زندگینامه زندگان]]'
},
'uw-block': {
autoblock: true,
expiry: '24 hours',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reasonParam: true,
summary: 'شما از ویرایش منع شدهاید', // localized
suppressArticleInSummary: true
},
'uw-blockindef': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reasonParam: true,
summary: 'شما به طور نامحدود از ویرایش منع شدهاید', // localized
suppressArticleInSummary: true
},
'uw-blocknotalk': {
disabletalk: true,
pageParam: true,
reasonParam: true,
summary: 'شما از ویرایش منع شدهاید و دسترسی به صفحه بحث کاربر غیرفعال شده است', // localized
suppressArticleInSummary: true
},
'uw-botblock': {
forRegisteredOnly: true,
pageParam: true,
reason: 'اجرای اسکریپت [[ویکیواژه:ربات|ربات]] بدون [[ویکیواژه:سیاست رباترانی/درخواست مجوز|مجوز]]', // localized
summary: 'You have been blocked from editing because it appears you are running a [[ویکیواژه:ربات]] without [[ویکیواژه:سیاست رباترانی/درخواست مجوز]]' // localized
},
'uw-botublock': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-botublock}} <!-- Username implies a bot, soft block -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما نشان می دهد که این یک حساب [[ویکیواژه:ربات|ربات]] است ، که در حال حاضر تأیید نشده است.' // localized
},
'uw-causeblock': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-causeblock}} <!-- Username represents a non-profit, soft block -->',
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما این تصور را ایجاد میکند که این حساب نماینده یک گروه، سازمان یا وب سایت است' // localized
},
'uw-compblock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: 'امنیت حساب کاربری', // localized
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا اعتقاد بر این است که [[ویکیواژه:امنیت حساب کاربری|امنیت حساب شما]] به خطر افتادهاست.' // localized
},
'uw-copyrightblock': {
autoblock: true,
expiry: '24 hours',
nocreate: true,
pageParam: true,
reason: '[[WP:Copyright violations|Copyright violations]]',
summary: 'برای ادامه [[WP:COPYVIO|نقض حق نسخه برداری]] از ویرایش منع شدهاید' // localized
},
'uw-dblock': {
autoblock: true,
nocreate: true,
reason: 'حذف مدام محتوا', // localized
pageParam: true,
summary: 'به دلیل [[ویکیواژه:خرابکاری|خرابکاری]] از ویرایش منع شدهاید' // localized
},
'uw-disruptblock': {
autoblock: true,
nocreate: true,
reason: '[[ویکیواژه:ویرایش اخلالگرانه|ویرایش اخلالگرانه]]', // localized
summary: 'به دلیل [[ویکیواژه:ویرایش اخلالگرانه|ویرایش اخلالگرانه]] از ویرایش منع شدهاید' // localized
},
'uw-efblock': {
autoblock: true,
nocreate: true,
reason: 'Deliberately triggering the [[ویکیواژه:پالایه ویرایش]]', // localized
summary: 'شما به دلیل ویرایشهای مخرب که مرتباً باعث [[ویکیواژه:پالایه ویرایش|پالایه ویرایش]] شدهاند، از ویرایش منع شدهاید.' // localized
},
'uw-ewblock': {
autoblock: true,
expiry: '24 hours',
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]]', // localized
summary: 'برای جلوگیری از [[ویکیواژه:ویرایش اخلالگرانه|اختلال]] بیشتر ناشی از درگیری شما در [[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]]، از ویرایش منع شدهاید.' // localized
},
'uw-hblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:حمله شخصی ممنوع|حمله شخصی ممنوع]] یا [[ویکیواژه:آزار و اذیت|آزار و اذیت]]', // localized
summary: 'شما به دلیل [[ویکیواژه:آزار و اذیت|آزار و اذیت]] سایر کاربران از ویرایش منع شدهاید.'
},
'uw-ipevadeblock': {
forAnonOnly: true,
nocreate: true,
reason: '[[ویکیواژه:قطع دسترسی|قطع دسترسی]]', // localized
summary: 'آدرس آیپی شما به دلیل [[ویکیواژه:قطع دسترسی|قطع دسترسی]] از ویرایش منع شدهاست.' // localized
},
'uw-lblock': {
autoblock: true,
expiry: 'infinity',
nocreate: true,
reason: 'Making [[ویکیواژه:تهدید قانونی ممنوع|تهدید قانونی ممنوع]]', // localized
summary: 'شما به دلیل [[ویکیواژه:تهدید قانونی ممنوع|تهدید قانونی ممنوع]] از ویرایش منع شدهاید.' // localized
},
'uw-nothereblock': {
autoblock: true,
expiry: 'infinity',
nocreate: true,
reason: 'روشن است که [[ویکیواژه:ما برای ساختن دانشنامه اینجا هستیم|برای ساختن دانشنامه اینجا نیستید.]]', // localized
forRegisteredOnly: true,
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا به نظر میرسد که شما در اینجا برای [[ویکیواژه:ما برای ساختن دانشنامه اینجا هستیم|ساختن دانشنامه]] نیستید' // localized
},
'uw-npblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: 'ایجاد مزخرفات ثبت اختراع یا سایر [[ویکیواژه:بیمعنی|صفحات نامناسب]]', // localized
summary: 'به دلیل ایجاد صفحات [[ویکیواژه:بیمعنی|بیمعنی]] از ویرایش منع شدهاید.' // localized
},
'uw-pablock': {
autoblock: true,
expiry: '31 hours',
nocreate: true,
reason: '[[ویکیواژه:حمله شخصی ممنوع|]] یا [[ویکیواژه:آزار و اذیت|]]', // localized
summary: 'شما به دلیل انجام [[ویکیواژه:حمله شخصی ممنوع|حملات شخصی]] به سایر کاربران از ویرایش منع شدهاید.' // localized
},
'uw-sblock': {
autoblock: true,
nocreate: true,
reason: 'استفاده از ویکیواژه به عنوان [[ویکیواژه:هرزنامه|هرزنامه]]', // localized
summary: 'به دلیل استفاده از ویکیواژه [[ویکیواژه:هرزنامه|هرزنامه]] از ویرایش منع شدهاید.' // localized
},
'uw-soablock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:هرزنامه]] / [[ویکیواژه:ویکیواژه چه چیزی نیست]]-تنها حساب', // localized
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا حساب شما فقط برای ایجاد [[ویکیواژه:هرزنامه|هرزنامه]] استفاده میشود.' // localized
},
'uw-socialmediablock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: 'استفاده از ویکیواژه به عنوان [[ویکیواژه:ویکیواژه چه چیزی نیست|یک وبلاگ، میزبان وب، سایت یا شبکههای اجتماعی]]', // localized
summary: 'شما به دلیل استفاده از صفحات کاربر و / یا مقاله به عنوان [[ویکیواژه:ویکیواژه چه چیزی نیست|وبلاگ، میزبان وب، سایت یا شبکههای اجتماعی]] از ویرایش منع شدهاید.' // localized
},
'uw-sockblock': {
autoblock: true,
forRegisteredOnly: true,
nocreate: true,
reason: 'سوءاستفاده از [[ویکیواژه:حساب زاپاس|چند حساب کاربری]]', // localized
summary: 'شما به دلیل سوء استفاده از [[ویکیواژه:حساب زاپاس|چند حساب کاربری]] ویرایش منع شدهاید ' // localized
},
'uw-softerblock': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-softerblock}} <!-- Promotional username, soft block -->',
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما این تصور را ایجاد میکند که این حساب نماینده یک گروه، سازمان یا وب سایت است' // localized
},
'uw-spamublock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: '{{Uw-spamublock}} <!-- Promotional username, promotional edits -->',
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا حساب شما فقط برای [[ویکیواژه:هرزنامه|هرزنامه یا تبلیغات]] استفاده میشود و [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما نقض خط مشی نام کاربری است.' // localized
},
'uw-spoablock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: '[[ویکیواژه:حساب زاپاس|حساب زاپاس]]', // localized
summary: 'این حساب به عنوان یک [[ویکیواژه:حساب زاپاس|حساب زاپاس]] ایجاد شده و براساس نقض خط مشی ویکی پد مسدود شده است.' // localized
},
'uw-talkrevoked': {
disabletalk: true,
reason: 'لغو دسترسی به صفحه بحث: استفاده نامناسب از صفحه بحث کاربر در حالی که مسدود شدهاست.', // localized
prependReason: true,
summary: 'دسترسی به صفحه گفتگوی کاربر غیرفعال شدهاست', // localized
useInitialOptions: true
},
'uw-ublock': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-ublock}} <!-- Username violation, soft block -->',
reasonParam: true,
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] ناقض قوانین است.' // localized
},
'uw-ublock-double': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-ublock-double}} <!-- Username closely resembles another user, soft block -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما بیش از حد به نام کاربری کاربر ویکیواژه دیگر شباهت دارد.' // localized
},
'uw-ucblock': {
autoblock: true,
expiry: '31 hours',
nocreate: true,
pageParam: true,
reason: ' [[راهنما:مقدمهای بر ارجاع/۱|افزودن محتوای بدون منبع]]', // localized
summary: 'به دلیل افزودن مداوم از [[راهنما:مقدمهای بر ارجاع/۱|محتوای بدون منبع]]، شما از ویرایش منع شدهاید.' // localized
},
'uw-uhblock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: '{{Uw-uhblock}} <!-- Username violation, hard block -->',
reasonParam: true,
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما نقض آشکار خط مشی نام کاربری است.' // localized
},
'uw-ublock-wellknown': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{بستن نام واقعی}} <!-- Username represents a well-known person, soft block -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما با نام یک فرد شناخته شده زنده مطابقت دارد.' // localized
},
'uw-uhblock-double': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: '{{Uw-ublock-double}} <!-- Username closely resembles another user, hard block -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا به نظر می رسد [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما نام یک کاربر دیگر ویکی پدیا را جعل میکند' // localized
},
'uw-upeblock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:فاشسازی مشارکت در برابر دستمزد]] in violation of the WMF [[WP:TOU|Terms of Use]]',
summary: 'شما به طور نامحدود به دلیل [[ویکیواژه:فاشسازی مشارکت در برابر دستمزد|فاشسازی مشارکت در برابر دستمزد]] از ویرایش منع شدهاید.' // localized
},
'uw-vaublock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reason: '{{Uw-vaublock}} <!-- Username violation, vandalism-only account -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:حساب ساختهشده برای خرابکاری|حساب شما]] برای خرابکاری ایجاد شدهاست و ناقض [[ویکیواژه:سیاست نام کاربری|سیاست نام کاربری]] است.' // localized
},
'uw-vblock': {
autoblock: true,
expiry: '31 hours',
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:خرابکاری|خرابکاری]]', // localized
summary: 'برای جلوگیری از بیشتر [[ویکیواژه:خرابکاری|خرابکاری]] شما از ویرایش منع شدهاید.' // localized
},
'uw-voablock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:حساب ساختهشده برای خرابکاری|حساب ساختهشده برای خرابکاری]]', // localized
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا حساب شما تنها برای [[ویکیواژه:حساب ساختهشده برای خرابکاری|خرابکاری]] استفاده میشود.' // localized
},
'zombie proxy': {
expiry: '1 month',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{زامبی پراکسی}}', // localized
sig: null
},
// Begin partial block templates, accessed in Twinkle.block.blockGroupsPartial
'uw-acpblock': {
autoblock: true,
expiry: '48 hours',
nocreate: true,
pageParam: false,
reasonParam: true,
reason: 'Misusing [[ویکیواژه:حساب زاپاس|حساب زاپاس]]', // localized
summary: 'به دلیل سوء استفاده از [[ویکیواژه:حساب زاپاس|چندین حساب]] از [[ویکیواژه:قطع دسترسی موردی|ایجاد حساب کاربری]] منع شدهاید.' // localized
},
'uw-acpblockindef': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: false,
reasonParam: true,
reason: 'سوء استفاده از [[ویکیواژه:حساب زاپاس|حساب زاپاس]]', // localized
summary: 'به دلیل سوء استفاده از [[ویکیواژه:حساب زاپاس|چندین حساب]] از [[ویکیواژه:قطع دسترسی موردی|ایجاد حساب کاربری]] منع شدهاید.' // localized
},
'uw-aepblock': {
autoblock: true,
nocreate: false,
pageParam: false,
reason: '[[WP:Arbitration enforcement|Arbitration enforcement]]',
reasonParam: true,
summary: 'به دلیل [[WP:Arbitration|نقض تصمیم داوری]] از ویرایش [[ویکیواژه:قطع دسترسی موردی|منع]] شده اید.' // localized
},
'uw-epblock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: false,
noemail: true,
pageParam: false,
reasonParam: true,
reason: '[[ویکیواژه:آزار و اذیت]] ایمیلی', // localized
summary: 'شما از ارسال ایمیل به ویرایشگران دیگر به دلیل [[ویکیواژه:آزار و اذیت|آزار و اذیت]] [[ویکیواژه:قطع دسترسی موردی|منع]] شدهاید' // localized
},
'uw-ewpblock': {
autoblock: true,
expiry: '24 hours',
nocreate: false,
pageParam: false,
reasonParam: true,
reason: '[[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]]', // localized
summary: 'شما از ویرایش مناطق خاصی از دانشنامه [[ویکیواژه:قطع دسترسی موردی|منع]] شدهاید تا از [[ویکیواژه:ویرایش اخلالگرانه|اختلال]] بیشتر ناشی از [[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]] جلوگیری شود.' // localized
},
'uw-pblock': {
autoblock: true,
expiry: '24 hours',
nocreate: false,
pageParam: false,
reasonParam: true,
summary: 'شما از ویرایش بخشهایی از دانشنامه [[ویکیواژه:قطع دسترسی مورد|منع]] شدهاید.' // localized
},
'uw-pblockindef': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: false,
pageParam: false,
reasonParam: true,
summary: 'شما از ویرایش بخشهایی از دانشنامه [[ویکیواژه:قطع دسترسی مورد|منع]] شدهاید.' // localized
}
};
Twinkle.block.transformBlockPresets = function twinkleblockTransformBlockPresets() {
// supply sensible defaults
$.each(Twinkle.block.blockPresetsInfo, function(preset, settings) {
settings.summary = settings.summary || settings.reason;
settings.sig = settings.sig !== undefined ? settings.sig : 'yes';
// despite this it's preferred that you use 'infinity' as the value for expiry
settings.indefinite = settings.indefinite || settings.expiry === 'infinity' || settings.expiry === 'infinite' || settings.expiry === 'indefinite' || settings.expiry === 'never';
if (!Twinkle.block.isRegistered && settings.indefinite) {
settings.expiry = '31 hours';
} else {
settings.expiry = settings.expiry || '31 hours';
}
Twinkle.block.blockPresetsInfo[preset] = settings;
});
};
// These are the groups of presets and defines the order in which they appear. For each list item:
// label: <string, the description that will be visible in the dropdown>
// value: <string, the key of a preset in blockPresetsInfo>
Twinkle.block.blockGroups = [
{
label: 'Common block reasons',
list: [
{ label: 'anonblock', value: 'anonblock' },
{ label: 'anonblock - likely a school', value: 'anonblock - school' },
{ label: 'school block', value: 'school block' },
{ label: 'Generic block (custom reason)', value: 'uw-block' }, // ends up being default for registered users
{ label: 'Generic block (custom reason) - IP', value: 'uw-ablock', selected: true }, // set only when blocking IP
{ label: 'Generic block (custom reason) - indefinite', value: 'uw-blockindef' },
{ label: 'Disruptive editing', value: 'uw-disruptblock' },
{ label: 'Inappropriate use of user talk page while blocked', value: 'uw-talkrevoked' },
{ label: 'Not here to build an encyclopedia', value: 'uw-nothereblock' },
{ label: 'Unsourced content', value: 'uw-ucblock' },
{ label: 'Vandalism', value: 'uw-vblock' },
{ label: 'Vandalism-only account', value: 'uw-voablock' }
]
},
{
label: 'Extended reasons',
list: [
{ label: 'Advertising', value: 'uw-adblock' },
{ label: 'Arbitration enforcement', value: 'uw-aeblock' },
{ label: 'Block evasion - IP', value: 'uw-ipevadeblock' },
{ label: 'BLP violations', value: 'uw-bioblock' },
{ label: 'Copyright violations', value: 'uw-copyrightblock' },
{ label: 'Creating nonsense pages', value: 'uw-npblock' },
{ label: 'Edit filter-related', value: 'uw-efblock' },
{ label: 'Edit warring', value: 'uw-ewblock' },
{ label: 'Generic block with talk page access revoked', value: 'uw-blocknotalk' },
{ label: 'Harassment', value: 'uw-hblock' },
{ label: 'Legal threats', value: 'uw-lblock' },
{ label: 'Personal attacks or harassment', value: 'uw-pablock' },
{ label: 'Possible compromised account', value: 'uw-compblock' },
{ label: 'Removal of content', value: 'uw-dblock' },
{ label: 'Sock puppetry (master)', value: 'uw-sockblock' },
{ label: 'Sock puppetry (puppet)', value: 'uw-spoablock' },
{ label: 'Social networking', value: 'uw-socialmediablock' },
{ label: 'Spam', value: 'uw-sblock' },
{ label: 'Spam/advertising-only account', value: 'uw-soablock' },
{ label: 'Unapproved bot', value: 'uw-botblock' },
{ label: 'Undisclosed paid editing', value: 'uw-upeblock' },
{ label: 'Violating the three-revert rule', value: 'uw-3block' }
]
},
{
label: 'Username violations',
list: [
{ label: 'Bot username', value: 'uw-botublock' },
{ label: 'Promotional username, hard block', value: 'uw-spamublock' },
{ label: 'Promotional username, soft block', value: 'uw-softerblock' },
{ label: 'Similar username soft block', value: 'uw-ublock-double' },
{ label: 'Username violation, soft block', value: 'uw-ublock' },
{ label: 'Username violation, hard block', value: 'uw-uhblock' },
{ label: 'Username impersonation hard block', value: 'uw-uhblock-double' },
{ label: 'Username represents a well-known person, soft block', value: 'uw-ublock-wellknown' },
{ label: 'Username represents a non-profit, soft block', value: 'uw-causeblock' },
{ label: 'Username violation, vandalism-only account', value: 'uw-vaublock' }
]
},
{
label: 'Templated reasons',
list: [
{ label: 'blocked proxy', value: 'blocked proxy' },
{ label: 'CheckUser block', value: 'CheckUser block', disabled: !Morebits.userIsInGroup('checkuser') },
{ label: 'checkuserblock-account', value: 'checkuserblock-account', disabled: !Morebits.userIsInGroup('checkuser') },
{ label: 'checkuserblock-wide', value: 'checkuserblock-wide', disabled: !Morebits.userIsInGroup('checkuser') },
{ label: 'colocationwebhost', value: 'colocationwebhost' },
{ label: 'oversightblock', value: 'oversightblock', disabled: !Morebits.userIsInGroup('oversight') },
// { label: 'rangeblock', value: 'rangeblock' }, // placeholder for when we add support for rangeblocks
{ label: 'spamblacklistblock', value: 'spamblacklistblock' },
{ label: 'tor', value: 'tor' },
{ label: 'webhostblock', value: 'webhostblock' },
{ label: 'zombie proxy', value: 'zombie proxy' }
]
}
];
Twinkle.block.blockGroupsPartial = [
{
label: 'Common partial block reasons',
list: [
{ label: 'Generic partial block (custom reason)', value: 'uw-pblock', selected: true },
{ label: 'Generic partial block (custom reason) - indefinite', value: 'uw-pblockindef' },
{ label: 'Edit warring', value: 'uw-ewpblock' }
]
},
{
label: 'Extended partial block reasons',
list: [
{ label: 'Arbitration enforcement', value: 'uw-aepblock' },
{ label: 'Email harassment', value: 'uw-epblock' },
{ label: 'Misusing multiple accounts', value: 'uw-acpblock' },
{ label: 'Misusing multiple accounts - indefinite', value: 'uw-acpblockindef' }
]
}
];
Twinkle.block.callback.filtered_block_groups = function twinkleblockCallbackFilteredBlockGroups(group, show_template) {
return $.map(group, function(blockGroup) {
var list = $.map(blockGroup.list, function(blockPreset) {
// only show uw-talkrevoked if reblocking
if (!Twinkle.block.currentBlockInfo && blockPreset.value === 'uw-talkrevoked') {
return;
}
var blockSettings = Twinkle.block.blockPresetsInfo[blockPreset.value];
var registrationRestrict = blockSettings.forRegisteredOnly ? Twinkle.block.isRegistered : blockSettings.forAnonOnly ? !Twinkle.block.isRegistered : true;
if (!(blockSettings.templateName && show_template) && registrationRestrict) {
var templateName = blockSettings.templateName || blockPreset.value;
return {
label: (show_template ? '{{' + templateName + '}}: ' : '') + blockPreset.label,
value: blockPreset.value,
data: [{
name: 'template-name',
value: templateName
}],
selected: !!blockPreset.selected,
disabled: !!blockPreset.disabled
};
}
});
if (list.length) {
return {
label: blockGroup.label,
list: list
};
}
});
};
Twinkle.block.callback.change_preset = function twinkleblockCallbackChangePreset(e) {
var key = e.target.form.preset.value;
if (!key) {
return;
}
e.target.form.template.value = Twinkle.block.blockPresetsInfo[key].templateName || key;
Twinkle.block.callback.update_form(e, Twinkle.block.blockPresetsInfo[key]);
Twinkle.block.callback.change_template(e);
};
Twinkle.block.callback.change_expiry = function twinkleblockCallbackChangeExpiry(e) {
var expiry = e.target.form.expiry;
if (e.target.value === 'custom') {
Morebits.quickForm.setElementVisibility(expiry.parentNode, true);
} else {
Morebits.quickForm.setElementVisibility(expiry.parentNode, false);
expiry.value = e.target.value;
}
};
Twinkle.block.seeAlsos = [];
Twinkle.block.callback.toggle_see_alsos = function twinkleblockCallbackToggleSeeAlso() {
var reason = this.form.reason.value.replace(
new RegExp('( <!--|;) ' + 'see also ' + Twinkle.block.seeAlsos.join(' and ') + '( -->)?'), ''
);
Twinkle.block.seeAlsos = Twinkle.block.seeAlsos.filter(function(el) {
return el !== this.value;
}.bind(this));
if (this.checked) {
Twinkle.block.seeAlsos.push(this.value);
}
var seeAlsoMessage = Twinkle.block.seeAlsos.join(' and ');
if (!Twinkle.block.seeAlsos.length) {
this.form.reason.value = reason;
} else if (reason.indexOf('{{') !== -1) {
this.form.reason.value = reason + ' <!-- see also ' + seeAlsoMessage + ' -->';
} else {
this.form.reason.value = reason + '; see also ' + seeAlsoMessage;
}
};
Twinkle.block.callback.update_form = function twinkleblockCallbackUpdateForm(e, data) {
var form = e.target.form, expiry = data.expiry;
// don't override original expiry if useInitialOptions is set
if (!data.useInitialOptions) {
if (Date.parse(expiry)) {
expiry = new Date(expiry).toGMTString();
form.expiry_preset.value = 'custom';
} else {
form.expiry_preset.value = data.expiry || 'custom';
}
form.expiry.value = expiry;
if (form.expiry_preset.value === 'custom') {
Morebits.quickForm.setElementVisibility(form.expiry.parentNode, true);
} else {
Morebits.quickForm.setElementVisibility(form.expiry.parentNode, false);
}
}
// boolean-flipped options, more at [[mw:API:Block]]
data.disabletalk = data.disabletalk !== undefined ? data.disabletalk : false;
data.hardblock = data.hardblock !== undefined ? data.hardblock : false;
// disable autoblock if blocking a bot
if (Twinkle.block.isRegistered && relevantUserName.search(/bot\b/i) > 0) {
data.autoblock = false;
}
$(form.field_block_options).find(':checkbox').each(function(i, el) {
// don't override original options if useInitialOptions is set
if (data.useInitialOptions && data[el.name] === undefined) {
return;
}
var check = data[el.name] === '' || !!data[el.name];
$(el).prop('checked', check);
});
if (data.prependReason && data.reason) {
form.reason.value = data.reason + '; ' + form.reason.value;
} else {
form.reason.value = data.reason || '';
}
};
Twinkle.block.callback.change_template = function twinkleblockcallbackChangeTemplate(e) {
var form = e.target.form, value = form.template.value, settings = Twinkle.block.blockPresetsInfo[value];
if (!$(form).find('[name=actiontype][value=block]').is(':checked')) {
if (settings.indefinite || settings.nonstandard) {
if (Twinkle.block.prev_template_expiry === null) {
Twinkle.block.prev_template_expiry = form.template_expiry.value || '';
}
form.template_expiry.parentNode.style.display = 'none';
form.template_expiry.value = 'indefinite';
} else if (form.template_expiry.parentNode.style.display === 'none') {
if (Twinkle.block.prev_template_expiry !== null) {
form.template_expiry.value = Twinkle.block.prev_template_expiry;
Twinkle.block.prev_template_expiry = null;
}
form.template_expiry.parentNode.style.display = 'block';
}
if (Twinkle.block.prev_template_expiry) {
form.expiry.value = Twinkle.block.prev_template_expiry;
}
Morebits.quickForm.setElementVisibility(form.notalk.parentNode, !settings.nonstandard);
Morebits.quickForm.setElementVisibility(form.noemail_template.parentNode, $(form).find('[name=actiontype][value=partial]').is(':checked') && !$(form).find('[name=actiontype][value=block]').is(':checked'));
Morebits.quickForm.setElementVisibility(form.nocreate_template.parentNode, $(form).find('[name=actiontype][value=partial]').is(':checked') && !$(form).find('[name=actiontype][value=block]').is(':checked'));
} else {
Morebits.quickForm.setElementVisibility(
form.blank_duration.parentNode,
!settings.indefinite && !settings.nonstandard
);
}
Morebits.quickForm.setElementVisibility(form.article.parentNode, !!settings.pageParam);
Morebits.quickForm.setElementVisibility(form.block_reason.parentNode, !!settings.reasonParam);
// Partial block
Morebits.quickForm.setElementVisibility(form.area.parentNode, $(form).find('[name=actiontype][value=partial]').is(':checked') && !$(form).find('[name=actiontype][value=block]').is(':checked'));
form.root.previewer.closePreview();
};
Twinkle.block.prev_template_expiry = null;
Twinkle.block.prev_block_reason = null;
Twinkle.block.prev_article = null;
Twinkle.block.prev_reason = null;
Twinkle.block.callback.preview = function twinkleblockcallbackPreview(form) {
var params = {
article: form.article.value,
blank_duration: form.blank_duration ? form.blank_duration.checked : false,
disabletalk: form.disabletalk.checked || (form.notalk ? form.notalk.checked : false),
expiry: form.template_expiry ? form.template_expiry.value : form.expiry.value,
hardblock: Twinkle.block.isRegistered ? form.autoblock.checked : form.hardblock.checked,
indefinite: (/indef|infinit|never|\*|max/).test(form.template_expiry ? form.template_expiry.value : form.expiry.value),
reason: form.block_reason.value,
template: form.template.value,
partial: $(form).find('[name=actiontype][value=partial]').is(':checked'),
pagerestrictions: $(form.pagerestrictions).val() || [],
namespacerestrictions: $(form.namespacerestrictions).val() || [],
noemail: form.noemail.checked || (form.noemail_template ? form.noemail_template.checked : false),
nocreate: form.nocreate.checked || (form.nocreate_template ? form.nocreate_template.checked : false),
area: form.area.value
};
var templateText = Twinkle.block.callback.getBlockNoticeWikitext(params);
form.previewer.beginRender(templateText, 'User_talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username
};
Twinkle.block.callback.evaluate = function twinkleblockCallbackEvaluate(e) {
var $form = $(e.target),
toBlock = $form.find('[name=actiontype][value=block]').is(':checked'),
toWarn = $form.find('[name=actiontype][value=template]').is(':checked'),
toPartial = $form.find('[name=actiontype][value=partial]').is(':checked'),
blockoptions = {}, templateoptions = {};
Twinkle.block.callback.saveFieldset($form.find('[name=field_block_options]'));
Twinkle.block.callback.saveFieldset($form.find('[name=field_template_options]'));
blockoptions = Twinkle.block.field_block_options;
templateoptions = Twinkle.block.field_template_options;
templateoptions.disabletalk = !!(templateoptions.disabletalk || blockoptions.disabletalk);
templateoptions.hardblock = !!blockoptions.hardblock;
delete blockoptions.expiry_preset; // remove extraneous
// Partial API requires this to be gone, not false or 0
if (toPartial) {
blockoptions.partial = templateoptions.partial = true;
}
templateoptions.pagerestrictions = $form.find('[name=pagerestrictions]').val() || [];
templateoptions.namespacerestrictions = $form.find('[name=namespacerestrictions]').val() || [];
// Format for API here rather than in saveFieldset
blockoptions.pagerestrictions = templateoptions.pagerestrictions.join('|');
blockoptions.namespacerestrictions = templateoptions.namespacerestrictions.join('|');
// use block settings as warn options where not supplied
templateoptions.summary = templateoptions.summary || blockoptions.reason;
templateoptions.expiry = templateoptions.template_expiry || blockoptions.expiry;
if (toBlock) {
if (blockoptions.partial) {
if (blockoptions.disabletalk && blockoptions.namespacerestrictions.indexOf('3') === -1) {
return alert('Partial blocks cannot prevent talk page access unless also restricting them from editing User talk space!');
}
if (!blockoptions.namespacerestrictions && !blockoptions.pagerestrictions) {
if (!blockoptions.noemail && !blockoptions.nocreate) { // Blank entries technically allowed [[phab:T208645]]
return alert('No pages or namespaces were selected, nor were email or account creation restrictions applied; please select at least one option to apply a partial block!');
} else if (!confirm('You are about to block with no restrictions on page or namespace editing, are you sure you want to proceed?')) {
return;
}
}
}
if (!blockoptions.expiry) {
return alert('Please provide an expiry!');
}
if (!blockoptions.reason) {
return alert('Please provide a reason for the block!');
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(e.target);
var statusElement = new Morebits.status('Executing block');
blockoptions.action = 'block';
blockoptions.user = mw.config.get('wgRelevantUserName');
// boolean-flipped options
blockoptions.anononly = blockoptions.hardblock ? undefined : true;
blockoptions.allowusertalk = blockoptions.disabletalk ? undefined : true;
/*
Check if block status changed while processing the form.
There's a lot to consider here. list=blocks provides the
current block status, but there are at least two issues with
relying on it. First, the id doesn't update on a reblock,
meaning the individual parameters need to be compared. This
can be done roughly with JSON.stringify - we can thankfully
rely on order from the server, although sorting would be
fine if not - but falsey values are problematic and is
non-ideal. More importantly, list=blocks won't indicate if a
non-blocked user is blocked then unblocked. This should be
exceedingy rare, but regardless, we thus need to check
list=logevents, which has a nicely updating logid
parameter. We can't rely just on that, though, since it
doesn't account for blocks that have expired on their own.
As such, we use both. Using some ternaries, the logid
variables are false if there's no logevents, so if they
aren't equal we defintely have a changed entry (send
confirmation). If they are equal, then either the user was
never blocked (the block statuses will be equal, no
confirmation) or there's no new block, in which case either
a block expired (different statuses, confirmation) or the
same block is still active (same status, no confirmation).
*/
api.get({
format: 'json',
action: 'query',
list: 'blocks|logevents',
letype: 'block',
lelimit: 1,
letitle: 'User:' + blockoptions.user,
bkusers: blockoptions.user
}).then(function(data) {
var block = data.query.blocks[0];
var logevents = data.query.logevents[0];
var logid = data.query.logevents.length ? logevents.logid : false;
if (logid !== Twinkle.block.blockLogId || !!block !== !!Twinkle.block.currentBlockInfo) {
var message = 'The block status of ' + mw.config.get('wgRelevantUserName') + ' has changed. ';
if (block) {
message += 'New status: ';
} else {
message += 'Last entry: ';
}
var logExpiry = '';
if (logevents.params.duration) {
if (logevents.params.duration === 'infinity') {
logExpiry = 'indefinitely';
} else {
var expiryDate = new Morebits.date(logevents.params.expiry);
logExpiry += (expiryDate.isBefore(new Date()) ? ', expired ' : ' until ') + expiryDate.calendar();
}
} else { // no duration, action=unblock, just show timestamp
logExpiry = ' ' + new Morebits.date(logevents.timestamp).calendar();
}
message += Morebits.string.toUpperCaseFirstChar(logevents.action) + 'ed by ' + logevents.user + logExpiry +
' for "' + logevents.comment + '". Do you want to override with your settings?';
if (!confirm(message)) {
Morebits.status.info('اعمال قطع دسترسی', 'توسط کاربر لغو شد'); // localized
return;
}
blockoptions.reblock = 1; // Writing over a block will fail otherwise
}
// execute block
blockoptions.token = mw.user.tokens.get('csrfToken');
var mbApi = new Morebits.wiki.api('اعمال قطع دسترسی', blockoptions, function() { // localized
statusElement.info('کامل شد'); // localized
if (toWarn) {
Twinkle.block.callback.issue_template(templateoptions);
}
});
mbApi.post();
});
} else if (toWarn) {
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(e.target);
Twinkle.block.callback.issue_template(templateoptions);
} else {
return alert('Please give Twinkle something to do!');
}
};
Twinkle.block.callback.issue_template = function twinkleblockCallbackIssueTemplate(formData) {
var userTalkPage = 'User_talk:' + mw.config.get('wgRelevantUserName');
var params = $.extend(formData, {
messageData: Twinkle.block.blockPresetsInfo[formData.template],
reason: Twinkle.block.field_template_options.block_reason,
disabletalk: Twinkle.block.field_template_options.notalk,
noemail: Twinkle.block.field_template_options.noemail_template,
nocreate: Twinkle.block.field_template_options.nocreate_template
});
Morebits.wiki.actionCompleted.redirect = userTalkPage;
Morebits.wiki.actionCompleted.notice = 'عمل انجام شد، بارگیری صفحهٔ بحث کاربر تا چند ثانیهٔ دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'User talk page modification');
wikipedia_page.setCallbackParameters(params);
wikipedia_page.setFollowRedirect(true);
wikipedia_page.load(Twinkle.block.callback.main);
};
Twinkle.block.callback.getBlockNoticeWikitext = function(params) {
var text = '{{', settings = Twinkle.block.blockPresetsInfo[params.template];
if (!settings.nonstandard) {
text += 'subst:' + params.template;
if (params.article && settings.pageParam) {
text += '|page=' + params.article;
}
if (!/te?mp|^\s*$|min/.exec(params.expiry)) {
if (params.indefinite) {
text += '|indef=yes';
} else if (!params.blank_duration) {
text += '|time=' + params.expiry;
}
}
if (!Twinkle.block.isRegistered && !params.hardblock) {
text += '|anon=yes';
}
if (params.reason) {
text += '|reason=' + params.reason;
}
if (params.disabletalk) {
text += '|notalk=yes';
}
// Currently, all partial block templates are "standard"
// Building the template, however, takes a fair bit of logic
if (params.partial) {
if (params.pagerestrictions.length || params.namespacerestrictions.length) {
var makeSentence = function (array) {
if (array.length < 3) {
return array.join(' and ');
}
var last = array.pop();
return array.join(', ') + ', and ' + last;
};
text += '|area=' + (params.indefinite ? 'certain ' : 'from certain ');
if (params.pagerestrictions.length) {
text += 'pages (' + makeSentence(params.pagerestrictions.map(function(p) {
return '[[:' + p + ']]';
}));
text += params.namespacerestrictions.length ? ') and certain ' : ')';
}
if (params.namespacerestrictions.length) {
// 1 => Talk, 2 => User, etc.
var namespaceNames = params.namespacerestrictions.map(function(id) {
return menuFormattedNamespaces[id];
});
text += '[[ویکیواژه:فضای نام]] (' + makeSentence(namespaceNames) + ')'; // localized
}
} else if (params.area) {
text += '|area=' + params.area;
} else {
if (params.noemail) {
text += '|email=yes';
}
if (params.nocreate) {
text += '|accountcreate=yes';
}
}
}
} else {
text += params.template;
}
if (settings.sig) {
text += '|sig=' + settings.sig;
}
return text + '}}';
};
Twinkle.block.callback.main = function twinkleblockcallbackMain(pageobj) {
var text = pageobj.getPageText(),
params = pageobj.getCallbackParameters(),
messageData = params.messageData,
date = new Morebits.date(pageobj.getLoadTime());
var dateHeaderRegex = date.monthHeaderRegex(), dateHeaderRegexLast, dateHeaderRegexResult;
while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) {
dateHeaderRegexResult = dateHeaderRegexLast;
}
// If dateHeaderRegexResult is null then lastHeaderIndex is never checked. If it is not null but
// \n== is not found, then the date header must be at the very start of the page. lastIndexOf
// returns -1 in this case, so lastHeaderIndex gets set to 0 as desired.
var lastHeaderIndex = text.lastIndexOf('\n==') + 1;
if (text.length > 0) {
text += '\n\n';
}
params.indefinite = (/indef|infinit|never|\*|max/).test(params.expiry);
if (Twinkle.getPref('blankTalkpageOnIndefBlock') && params.template !== 'uw-lblock' && params.indefinite) {
Morebits.status.info('اطلاعرسانی', 'صفحهٔ بحث کاربر به درخواست شما خالی شد و یک بخش با عنوان سطح ۲ و تاریخ امروز در آن افزوده شد'); // localiezd
text = date.monthHeader() + '\n';
} else if (!dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex) {
Morebits.status.info('اطلاعرسانی', 'یک بخش جدید با عنوان سطح ۲ و تاریخ امروز در صفحهٔ بحث افزوده خواهد شد، چرا که بخش مشابهی برای ماه جاری یافت نشد'); // localized
text += date.monthHeader() + '\n';
}
params.expiry = typeof params.template_expiry !== 'undefined' ? params.template_expiry : params.expiry;
text += Twinkle.block.callback.getBlockNoticeWikitext(params);
// build the edit summary
var summary = messageData.summary;
if (messageData.suppressArticleInSummary !== true && params.article) {
summary += ' بر [[:' + params.article + ']]'; // localized
}
summary += '.' + Twinkle.getPref('summaryAd');
pageobj.setPageText(text);
pageobj.setEditSummary(summary);
pageobj.setWatchlist(Twinkle.getPref('watchWarnings'));
pageobj.save();
};
})(jQuery);
// </nowiki>
/* ===== friendlywelcome.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** friendlywelcome.js: Welcome module
****************************************
* Mode of invocation: Tab ("Wel"), or from links on diff pages
* Active on: Any page with relevant user name (userspace,
* contribs, etc.) and diff pages
*/
Twinkle.welcome = function friendlywelcome() {
if (mw.util.getParamValue('friendlywelcome')) {
if (mw.util.getParamValue('friendlywelcome') === 'auto') {
Twinkle.welcome.auto();
} else {
Twinkle.welcome.semiauto();
}
} else {
Twinkle.welcome.normal();
}
};
Twinkle.welcome.auto = function() {
if (mw.util.getParamValue('action') !== 'edit') {
// userpage not empty, aborting auto-welcome
return;
}
Twinkle.welcome.welcomeUser();
};
Twinkle.welcome.semiauto = function() {
Twinkle.welcome.callback(mw.config.get('wgRelevantUserName'));
};
Twinkle.welcome.normal = function() {
if (mw.util.getParamValue('diff')) {
// check whether the contributors' talk pages exist yet
var $oList = $('#mw-diff-otitle2').find('span.mw-usertoollinks a.new:contains(talk)').first();
var $nList = $('#mw-diff-ntitle2').find('span.mw-usertoollinks a.new:contains(talk)').first();
if ($oList.length > 0 || $nList.length > 0) {
var spanTag = function(color, content) {
var span = document.createElement('span');
span.style.color = color;
span.appendChild(document.createTextNode(content));
return span;
};
var welcomeNode = document.createElement('strong');
var welcomeLink = document.createElement('a');
welcomeLink.appendChild(spanTag('Black', '['));
welcomeLink.appendChild(spanTag('Goldenrod', 'خوشامد')); // localized
welcomeLink.appendChild(spanTag('Black', ']'));
welcomeNode.appendChild(welcomeLink);
if ($oList.length > 0) {
var oHref = $oList.attr('href');
var oWelcomeNode = welcomeNode.cloneNode(true);
oWelcomeNode.firstChild.setAttribute('href', oHref + '&' + $.param({
'friendlywelcome': Twinkle.getPref('quickWelcomeMode') === 'auto' ? 'auto' : 'norm',
'vanarticle': Morebits.pageNameNorm
}));
$oList[0].parentNode.parentNode.appendChild(document.createTextNode(' '));
$oList[0].parentNode.parentNode.appendChild(oWelcomeNode);
}
if ($nList.length > 0) {
var nHref = $nList.attr('href');
var nWelcomeNode = welcomeNode.cloneNode(true);
nWelcomeNode.firstChild.setAttribute('href', nHref + '&' + $.param({
'friendlywelcome': Twinkle.getPref('quickWelcomeMode') === 'auto' ? 'auto' : 'norm',
'vanarticle': Morebits.pageNameNorm
}));
$nList[0].parentNode.parentNode.appendChild(document.createTextNode(' '));
$nList[0].parentNode.parentNode.appendChild(nWelcomeNode);
}
}
}
if (mw.config.get('wgRelevantUserName')) {
Twinkle.addPortletLink(function() {
Twinkle.welcome.callback(mw.config.get('wgRelevantUserName'));
}, 'خوشامد', 'friendly-welcome', 'خوشامدگویی به کاربر'); // localized
}
};
Twinkle.welcome.welcomeUser = function welcomeUser() {
Morebits.status.init(document.getElementById('mw-content-text'));
$('#catlinks').remove();
var params = {
value: Twinkle.getPref('quickWelcomeTemplate'),
article: mw.util.getParamValue('vanarticle') || '',
mode: 'auto'
};
var userTalkPage = mw.config.get('wgFormattedNamespaces')[3] + ':' + mw.config.get('wgRelevantUserName');
Morebits.wiki.actionCompleted.redirect = userTalkPage;
Morebits.wiki.actionCompleted.notice = 'خوشامدگویی انجام شد، در حال بارگزاری دوبارهٔ صفحه بحث تا چند ثانیهٔ دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'ویرایش صفحهٔ بحث کاربر'); // localized
wikipedia_page.setFollowRedirect(true);
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.welcome.callbacks.main);
};
Twinkle.welcome.callback = function friendlywelcomeCallback(uid) {
if (uid === mw.config.get('wgUserName') && !confirm('آیا از خوشامدگویی به خودتان مطمئن هستید؟...')) { // localized
return;
}
var Window = new Morebits.simpleWindow(600, 420);
Window.setTitle('خوشامدگویی به کاربر'); // localized
Window.setScriptName('توینکل'); // localiezd
// Window.addFooterLink('Welcoming Committee', 'WP:WC'); // localized [removed]
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#welcome'); // localized
var form = new Morebits.quickForm(Twinkle.welcome.callback.evaluate);
form.append({
type: 'select',
name: 'type',
label: 'نوع خوشامدگویی: ',
event: Twinkle.welcome.populateWelcomeList,
list: [
{ type: 'option', value: 'standard', label: 'خوشامدهای استاندارد', selected: !mw.util.isIPAddress(mw.config.get('wgRelevantUserName')) }, // localized
{ type: 'option', value: 'anonymous', label: 'خوشامد به کاربران ناشناس', selected: mw.util.isIPAddress(mw.config.get('wgRelevantUserName')) } // , // localized
// { type: 'option', value: 'wikiProject', label: 'WikiProject welcomes' }, // localized [removed]
// { type: 'option', value: 'nonEnglish', label: 'Non-English welcomes' } // localiezd [removed]
]
});
form.append({
type: 'div',
id: 'welcomeWorkArea',
className: 'morebits-scrollbox'
});
form.append({
type: 'input',
name: 'article',
label: '* مقالهٔ پیوند شده (در صورت پشتیبانی توسط الگو):', // localized
value: mw.util.getParamValue('vanarticle') || '',
tooltip: 'در صورت پشتیبانی توسط الگو، میتوان یک مقاله را از درون پیام خوشامد پیوند داد. برای اینکه به هیچ مقالهای پیوند داده نشود، جعبه را خالی بگذارید. الگوهایی که از پیونددهی به یک مقاله پشتیبانی میکنند با یک ستاره علامتگذاری شدهاند.' // localized
});
var previewlink = document.createElement('a');
$(previewlink).click(function() {
Twinkle.welcome.callbacks.preview(result); // |result| is defined below
});
previewlink.style.cursor = 'pointer';
previewlink.textContent = 'پیشنمایش'; // localized
form.append({ type: 'div', name: 'welcomepreview', label: [ previewlink ] });
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
// initialize the welcome list
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.type.dispatchEvent(evt);
};
Twinkle.welcome.populateWelcomeList = function(e) {
var type = e.target.value;
var container = new Morebits.quickForm.element({ type: 'fragment' });
if ((type === 'standard' || type === 'anonymous') && Twinkle.getPref('customWelcomeList').length) {
container.append({ type: 'header', label: 'الگوهای خوشامد سفارشی' }); // localized
container.append({
type: 'radio',
name: 'template',
list: Twinkle.getPref('customWelcomeList'),
event: function() {
e.target.form.article.disabled = false;
}
});
}
var sets = Twinkle.welcome.templates[type];
$.each(sets, function(label, templates) {
container.append({ type: 'header', label: label });
container.append({
type: 'radio',
name: 'template',
list: $.map(templates, function(properties, template) {
return {
value: template,
label: '{{' + template + '}}: ' + properties.description + (properties.linkedArticle ? '\u00A0*' : ''), // U+00A0 NO-BREAK SPACE
tooltip: properties.tooltip // may be undefined
};
}),
event: function(ev) {
ev.target.form.article.disabled = !templates[ev.target.value].linkedArticle;
}
});
});
var rendered = container.render();
$(e.target.form).find('div#welcomeWorkArea').empty().append(rendered);
var firstRadio = e.target.form.template[0];
firstRadio.checked = true;
var vals = sets[Object.keys(sets)[0]];
e.target.form.article.disabled = vals[firstRadio.value] ? !vals[firstRadio.value].linkedArticle : true;
};
// A list of welcome templates and their properties and syntax
// The four fields that are available are "description", "linkedArticle", "syntax", and "tooltip".
// The three magic words that can be used in the "syntax" field are:
// - $USERNAME$ - replaced by the welcomer's username, depending on user's preferences
// - $ARTICLE$ - replaced by an article name, if "linkedArticle" is true
// - $HEADER$ - adds a level 2 header (most templates already include this)
Twinkle.welcome.templates = {
'standard': {
'الگوی خوشامد': { // localized
'خوشامد': { // localized
description: 'خوشامد استاندارد', // localized
syntax: '{{subst:خوشامد}} ~~~~' // localized
}
}
},
'anonymous': {
'الگوی خوشامد': { // localized
'خوشامد': { // localized
description: 'خوشامد استاندارد', // localized
syntax: '{{subst:خوشامد}} ~~~~' // localized
}
}
}
};
Twinkle.welcome.getTemplateWikitext = function(type, template, article) {
// the iteration is required as the type=standard has two groups
var properties;
$.each(Twinkle.welcome.templates[type], function(label, templates) {
properties = templates[template];
if (properties) {
return false; // break
}
});
if (properties) {
return properties.syntax.
replace('$USERNAME$', Twinkle.getPref('insertUsername') ? mw.config.get('wgUserName') : '').
replace('$ARTICLE$', article ? article : '').
replace(/\$HEADER\$\s*/, '== Welcome ==\n\n').
replace('$EXTRA$', ''); // EXTRA is not implemented yet
}
return '{{subst:' + template + (article ? '|art=' + article : '') + '}}' +
(Twinkle.getPref('customWelcomeSignature') ? ' ~~~~' : '');
};
Twinkle.welcome.callbacks = {
preview: function(form) {
var previewDialog = new Morebits.simpleWindow(750, 400);
previewDialog.setTitle('پیشنمایش الگوی خوشامد'); // localized
previewDialog.setScriptName('خوشامد به کاربر'); // localized
previewDialog.setModality(true);
var previewdiv = document.createElement('div');
previewdiv.style.marginLeft = previewdiv.style.marginRight = '0.5em';
previewdiv.style.fontSize = 'small';
previewDialog.setContent(previewdiv);
var previewer = new Morebits.wiki.preview(previewdiv);
previewer.beginRender(Twinkle.welcome.getTemplateWikitext(form.type.value, form.getChecked('template'), form.article.value), 'User talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username
var submit = document.createElement('input');
submit.setAttribute('type', 'submit');
submit.setAttribute('value', 'Close');
previewDialog.addContent(submit);
previewDialog.display();
$(submit).click(function() {
previewDialog.close();
});
},
main: function(pageobj) {
var params = pageobj.getCallbackParameters();
var text = pageobj.getPageText();
// abort if mode is auto and form is not empty
if (pageobj.exists() && params.mode === 'auto') {
Morebits.status.info('Warning', 'صفحهٔ بحث کاربر خالی نیست؛ در حال چشمپوشی از خوشامدگویی خودکار'); // localized
Morebits.wiki.actionCompleted.event();
return;
}
var welcomeText = Twinkle.welcome.getTemplateWikitext(params.type, params.value, params.article);
if (Twinkle.getPref('topWelcomes')) {
text = welcomeText + '\n\n' + text;
} else {
text += '\n' + welcomeText;
}
var summaryText = 'به ویکیواژه خوشآمدید!'; // localized
pageobj.setPageText(text);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchWelcomes'));
pageobj.setCreateOption('recreate');
pageobj.save();
}
};
Twinkle.welcome.callback.evaluate = function friendlywelcomeCallbackEvaluate(e) {
var form = e.target;
var params = {
type: form.type.value,
value: form.getChecked('template'),
article: form.article.value,
mode: 'manual'
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
var userTalkPage = mw.config.get('wgFormattedNamespaces')[3] + ':' + mw.config.get('wgRelevantUserName');
Morebits.wiki.actionCompleted.redirect = userTalkPage;
Morebits.wiki.actionCompleted.notice = 'خوشامدگویی انجام شد، در حال بارگزاری دوبارهٔ صفحهٔ بحث تا چند لحظه دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'User talk page modification');
wikipedia_page.setFollowRedirect(true);
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.welcome.callbacks.main);
};
})(jQuery);
// </nowiki>
/* ===== friendlyshared.js ===== */
// <nowiki>
(function($) { // eslint-disable-line no-unused-vars
/*
****************************************
*** friendlyshared.js: Shared IP tagging module
****************************************
* Mode of invocation: Tab ("Shared")
* Active on: Existing IP user talk pages
*/
Twinkle.shared = function friendlyshared() {
if (mw.config.get('wgNamespaceNumber') === 3 && mw.util.isIPAddress(mw.config.get('wgTitle'))) {
var username = mw.config.get('wgRelevantUserName');
Twinkle.addPortletLink(function() {
Twinkle.shared.callback(username);
}, 'آیپی اشتراکی', 'friendly-shared', 'برچسبزدن آیپیهای اشتراکی'); // localized
}
};
Twinkle.shared.callback = function friendlysharedCallback() {
var Window = new Morebits.simpleWindow(600, 420);
Window.setTitle('برچسب زدن نشانیهای آیپی اشتراکی'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#shared'); // localiezd
var form = new Morebits.quickForm(Twinkle.shared.callback.evaluate);
var div = form.append({
type: 'div',
id: 'sharedip-templatelist',
className: 'morebits-scrollbox'
}
);
div.append({ type: 'header', label: 'الگوهای نشانی آیپی اشتراکی' }); // localized
div.append({ type: 'radio', name: 'shared', list: Twinkle.shared.standardList,
event: function(e) {
Twinkle.shared.callback.change_shared(e);
e.stopPropagation();
}
});
var org = form.append({ type: 'field', label: 'سایر جزئیات (اختیاری) را پر کنید و روی دکمهٔ پایین فرم کلیک کنید' }); // localized
org.append({
type: 'input',
name: 'organization',
label: 'مالک/گردانندهٔ نشانی آیپی', // localized
disabled: true,
tooltip: 'میتوانید به صورت اختیاری نام سازمانی را که مالک/گردانندهٔ نشانی آیپی است، وارد کنید. در صورت لزوم میتوانید از ویکیکد استفاده کنید.' // localized
}
);
org.append({
type: 'input',
name: 'host',
label: 'نام میزبان (اختیاری)', // localized
disabled: true,
tooltip: 'نام میزبان (نظیر proxy.example.com) میتواند به صورت اختیاری در اینجا وارد شود و با الگو پیوند خواهد یافت.' // localized
}
);
org.append({
type: 'input',
name: 'contact',
label: 'اطلاعات تماس (فقط در صورت درخواست)', // localized
disabled: true,
tooltip: 'میتوانید به طور اختیاری برخی از اطلاعات تماس را برای آن سازمان وارد کنید. از این پارامتر فقط در صورتی استفاده کنید که سازمان بهطور خاص درخواست کرده است که آن را اضافه کنید. در صورت لزوم میتوانید از ویکیکد استفاده کنید.' // localized
}
);
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
};
Twinkle.shared.standardList = [
{
label: '{{آیپی مشترک}}: الگوی استاندارد نشانی آیپی مشترک', // localized
value: 'آیپی مشترک', // localized
tooltip: 'الگوی صفحه بحث کاربر آیپی که اطلاعات مفیدی را برای کاربران آیپی و کسانی که مایل به هشدار به آنها یا بستن یا تحریم آنها هستند نشان میدهد' // localized
},
{
label: '{{آیپی مشترک آموزشی}}: الگوی اصلاحشده نشانی آیپی مشترک برای مؤسسات آموزشی', // localized
value: 'آیپی مشترک آموزشی' // localized
},
{
label: '{{آیپی مشترک شرکتی}}: الگوی اصلاحشده نشانی آیپی مشترک برای مشاغل', // localized
value: 'آیپی مشترک آموزشی' // localized
},
{
label: '{{نشانی آیپی مشترک (عمومی)}}: الگوی اصلاحشده نشانی آیپی مشترک برای پایانههای عمومی', // localized
value: 'آیپی مشترک (عمومی)' // localized
},
{
label: '{{آیپی مشترک دولتی}}: الگوی اصلاحشده نشانی آیپی مشترک برای آژانسهای دولتی یا دفترهای خدماتی', // localized
value: 'آیپی مشترک دولتی' // localized
},
{
label: '{{آیپی پویا}}: الگوی اصلاحشده نشانی آیپی مشترک برای سازمانهایی با آدرسدهی پویا', // localized
value: 'آیپی پویا' // localized
},
{
label: '{{آیپی ایستا}}: الگوی اصلاحشده نشانی آیپی مشترک برای آدرسهای آیپی ثابت', // localized
value: 'آیپی ایستا' // localized
},
{
label: '{{آیاسپی}}: الگوی اصلاحشده نشانی آیپی مشترک برای سازمانهای ISP (به طور خاص پراکسی)', // localized
value: 'آیاسپی' // localized
},
{
label: '{{آیپی تلفن همراه}}:الگوی اصلاحشده نشانی آیپی مشترک برای شرکتهای تلفن همراه و مشتریان آنها', // localized
value: 'آیپی تلفن همراه' // localized
},
{
label: '{{Whois}}: الگوی آدرسهای آیپی که نیاز به نظارت دارند، اما معلوم نیست که ایستا، پویا یا مشترک هستند', // localized
value: 'Whois'
}
];
Twinkle.shared.callback.change_shared = function friendlysharedCallbackChangeShared(e) {
e.target.form.contact.disabled = e.target.value !== 'آیپی مشترک آموزشی'; // only supported by {{Shared IP edu}} // localized
e.target.form.organization.disabled = false;
e.target.form.host.disabled = e.target.value === 'Whois'; // host= not supported by {{Whois}}
};
Twinkle.shared.callbacks = {
main: function(pageobj) {
var params = pageobj.getCallbackParameters();
var pageText = pageobj.getPageText();
var found = false;
var text = '{{';
for (var i = 0; i < Twinkle.shared.standardList.length; i++) {
var tagRe = new RegExp('(\\{\\{' + Twinkle.shared.standardList[i].value + '(\\||\\}\\}))', 'im');
if (tagRe.exec(pageText)) {
Morebits.status.warn('Info', 'الگوی {{' + Twinkle.shared.standardList[i].value + '}} روی صفحهٔ بحث پیدا شد؛ نصب الگو متوقف شد.'); // localized
found = true;
}
}
if (found) {
return;
}
Morebits.status.info('Info', 'الگوی نشانی آیپی مشترک را در بالای صفحه بحث کاربر اضافه میکند.'); // localized
text += params.value + '|' + params.organization;
if (params.value === 'Shared IP edu' && params.contact !== '') {
text += '|' + params.contact;
}
if (params.value !== 'Whois' && params.host !== '') {
text += '|host=' + params.host;
}
text += '}}\n\n';
var summaryText = 'Added {{[[Template:' + params.value + '|' + params.value + ']]}} template.';
pageobj.setPageText(text + pageText);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setMinorEdit(Twinkle.getPref('markSharedIPAsMinor'));
pageobj.setCreateOption('recreate');
pageobj.save();
}
};
Twinkle.shared.callback.evaluate = function friendlysharedCallbackEvaluate(e) {
var shared = e.target.getChecked('shared');
if (!shared || shared.length <= 0) {
alert('برای استفاده باید یک الگوی نشانی آیپی مشترک انتخاب کنید!'); // localized
return;
}
var value = shared[0];
if (e.target.organization.value === '') {
alert('برای الگوی {{' + value + '}} باید نام سازمان را وارد کنید!'); // localized
return;
}
var params = {
value: value,
organization: e.target.organization.value,
host: e.target.host.value,
contact: e.target.contact.value
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(e.target);
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'برچسب زدن کامل شد، بارگیری صفحه بحث ظرف چند ثانیهٔ دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'اصلاح صفحهٔ بحث کاربر'); // localized
wikipedia_page.setFollowRedirect(true);
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.shared.callbacks.main);
};
})(jQuery);
// </nowiki>
/* ===== friendlytalkback.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** friendlytalkback.js: Talkback module
****************************************
* Mode of invocation: Tab ("TB")
* Active on: Any page with relevant user name (userspace, contribs, etc.)
* Config directives in: FriendlyConfig
*/
Twinkle.talkback = function() {
if (!mw.config.get('wgRelevantUserName')) {
return;
}
Twinkle.addPortletLink(Twinkle.talkback.callback, 'بازبحث', 'friendly-talkback', 'گفتگوی آسان'); // localized
};
Twinkle.talkback.callback = function() {
if (mw.config.get('wgRelevantUserName') === mw.config.get('wgUserName') && !confirm("اوضاع این قدر بد است که میخواهید با خودتان گفتگو کنید؟")) { // localized
return;
}
var Window = new Morebits.simpleWindow(600, 350);
Window.setTitle('بازبحث'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('دربارهٔ {{بازبحث}}', 'الگو:بازبحث'); //localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#talkback'); //localized
var form = new Morebits.quickForm(Twinkle.talkback.evaluate);
form.append({ type: 'radio', name: 'tbtarget',
list: [
{
label: 'بازبحث: صفحهٔ بحث من', //localized
value: 'mytalk',
checked: 'true'
},
{
label: 'بازبحث: صفحهٔ بحث کاربر دیگر', //localized
value: 'usertalk'
},
{
label: 'بازبحث: صفحهای دیگر', //localized
value: 'other'
},
{
label: '«لطفاً ببینید»', //localized
value: 'see'
},
{
label: 'آگاهسازی درخصوص تابلوی اعلانات', //localized
value: 'notice'
},
{
label: '«رایانامه دارید»', //localized
value: 'mail'
}
],
event: Twinkle.talkback.changeTarget
});
form.append({
type: 'field',
label: 'Work area',
name: 'work_area'
});
var previewlink = document.createElement('a');
$(previewlink).click(function() {
Twinkle.talkback.preview(result); // |result| is defined below
});
previewlink.style.cursor = 'pointer';
previewlink.textContent = 'پیشنمایش'; // localized
form.append({ type: 'div', id: 'talkbackpreview', label: [ previewlink ] });
form.append({ type: 'div', id: 'friendlytalkback-previewbox', style: 'display: none' });
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
result.previewer = new Morebits.wiki.preview($(result).find('div#friendlytalkback-previewbox').last()[0]);
// We must init the
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.tbtarget[0].dispatchEvent(evt);
// Check whether the user has opted out from talkback
var query = {
action: 'query',
prop: 'extlinks',
titles: 'بحث کاربر:' + mw.config.get('wgRelevantUserName'), //localized
elquery: 'userjs.invalid/noTalkback',
ellimit: '1'
};
var wpapi = new Morebits.wiki.api('دریافت وضعیت استثنا از بازبحث', query, Twinkle.talkback.callback.optoutStatus); // localized
wpapi.post();
};
Twinkle.talkback.optout = '';
Twinkle.talkback.callback.optoutStatus = function(apiobj) {
var $el = $(apiobj.getXML()).find('el');
if ($el.length) {
Twinkle.talkback.optout = mw.config.get('wgRelevantUserName') + ' ترجیح میدهد بازبحث دریافت نکند'; // localized
var url = $el.text();
var reason = mw.util.getParamValue('reason', url);
Twinkle.talkback.optout += reason ? ': ' + reason : '.';
}
$('#twinkle-talkback-optout-message').text(Twinkle.talkback.optout);
};
var prev_page = '';
var prev_section = '';
var prev_message = '';
Twinkle.talkback.changeTarget = function(e) {
var value = e.target.values;
var root = e.target.form;
var old_area = Morebits.quickForm.getElements(root, 'work_area')[0];
if (root.section) {
prev_section = root.section.value;
}
if (root.message) {
prev_message = root.message.value;
}
if (root.page) {
prev_page = root.page.value;
}
var work_area = new Morebits.quickForm.element({
type: 'field',
label: 'اطلاعات بازبحث', // localized
name: 'work_area'
});
root.previewer.closePreview();
switch (value) {
case 'mytalk':
/* falls through */
default:
work_area.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-talkback-optout-message'
});
work_area.append({
type: 'input',
name: 'section',
label: 'عنوان بخش (اختیاری)', // localized
tooltip: 'عنوان بخشی از صفحهٔ بحث شما که در آن برای کاربر پیام گذاشتهاید. اگر چنین بخشی وجود ندارد این جعبه را خالی بگذارید.', // localized
value: prev_section
});
break;
case 'usertalk':
work_area.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-talkback-optout-message'
});
work_area.append({
type: 'input',
name: 'page',
label: 'کاربر (اجباری)', // localized
tooltip: 'نام کاربری که در صفحهٔ بحثش پیام گذاشتهاید. این پارامتر اجباری است.', // localized
value: prev_page,
required: true
});
work_area.append({
type: 'input',
name: 'section',
label: 'عنوان بخش (اختیاری)', // localized
tooltip: 'عنوان بخشی از صفحه که در آن پیام گذاشتهاید. اگر چنین بخشی وجود ندارد این پارامتر را خالی بگذارید.', // localized
value: prev_section
});
break;
case 'notice':
var noticeboard = work_area.append({
type: 'select',
name: 'noticeboard',
label: 'تابلوی اعلانات:', // localized
event: function(e) {
if (e.target.value === 'afchd') {
Morebits.quickForm.overrideElementLabel(e.target.form.section, 'عنوان پیشنویس (بدون پیشوند «پیشنویس»): '); // localized
Morebits.quickForm.setElementTooltipVisibility(e.target.form.section, false);
} else {
Morebits.quickForm.resetElementLabel(e.target.form.section);
Morebits.quickForm.setElementTooltipVisibility(e.target.form.section, true);
}
}
});
$.each(Twinkle.talkback.noticeboards, function(value, data) {
noticeboard.append({
type: 'option',
label: data.label,
value: value,
selected: !!data.defaultSelected
});
});
work_area.append({
type: 'input',
name: 'section',
label: 'ریسهٔ پیوندشده', // localized
tooltip: 'عنوان ریسهٔ مرتبط در تابلوی اعلانات.', // localized
value: prev_section
});
break;
case 'other':
work_area.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-talkback-optout-message'
});
work_area.append({
type: 'input',
name: 'page',
label: 'نام کامل صفحه (اجباری)', // localized
tooltip: "نام کامل صفحهای که در آن برای کاربر پیام گذاشتهاید. نام را با پیشوند وارد کنید، مثلاً «ویکیواژه:قهوهخانه/فنی». این پارامتر اجباری است.", // localized
value: prev_page,
required: true
});
work_area.append({
type: 'input',
name: 'section',
label: 'عنوان بخش (اختیاری)', // localized
tooltip: 'عنوان بخشی از صفحه که در آن پیام گذاشتهاید. اگر چنین بخشی وجود ندارد این پارامتر را خالی بگذارید.', // localized
value: prev_section
});
break;
case 'mail':
work_area.append({
type: 'input',
name: 'section',
label: 'موضوع رایانامه (اختیاری)', // localized
tooltip: 'عبارتی که به عنوان موضوع ایمیل ذکر کردید.' // localized
});
break;
case 'see':
work_area.append({
type: 'input',
name: 'page',
label: 'نام کامل صفحه (اجباری)', // localized
tooltip: "نام کامل صفحهای که در آن برای کاربر پیام گذاشتهاید. نام را با پیشوند وارد کنید، مثلاً «ویکیواژه:قهوهخانه/فنی». این پارامتر اجباری است.", // localized
value: prev_page,
required: true
});
work_area.append({
type: 'input',
name: 'section',
label: 'عنوان بخش (اختیاری)', // localized
tooltip: 'عنوان بخشی از صفحه که در آن پیام گذاشتهاید. اگر چنین بخشی وجود ندارد این پارامتر را خالی بگذارید.', // localized
value: prev_section
});
break;
}
if (value !== 'notice') {
work_area.append({ type: 'textarea', label: 'پیام اضافه (اختیاری):', name: 'message', tooltip: 'پیام اضافهای که میخواهید زیر الگوی بازبحث نوشته شود. امضای شما به طور خودکار به انتهای پیام اضافه خواهد شد.' }); // localized
}
work_area = work_area.render();
root.replaceChild(work_area, old_area);
if (root.message) {
root.message.value = prev_message;
}
$('#twinkle-talkback-optout-message').text(Twinkle.talkback.optout);
};
Twinkle.talkback.noticeboards = {
'an': {
label: "وو:تام (تابلوی اعلانات مدیران)", // localized
text: '== ' + Twinkle.getPref('adminNoticeHeading') + ' ==\n' +
"{{subst:آگاهسازی تام|بخش=$SECTION}} ~~~~", // localized
editSummary: 'آگاهسازی از بحثی که در [[ویکیواژه:تابلوی اعلانات مدیران]] مطرح شدهاست' // localized
},
'an3': {
label: "وو:ن۳ب (تابلوی اعلانات جنگ ویرایشی)", // localized
text: '{{subst:آگاهسازی تام۳|$SECTION}} ~~~~', // localized
editSummary: "آگاهسازی از شکایتی که در [[ویکیواژه:تابلوی اعلانات مدیران/نقض ۳ برگردان]] مطرح شدهاست." // localized
},
'ani': {
label: "شکایت در وو:تام (تابلوی اعلانات مدیران)", // localized
text: '== ' + Twinkle.getPref('adminNoticeHeading') + ' ==\n' +
"{{subst:آگاهسازی شکایت در تام|بخش=$SECTION}} ~~~~", // localized
editSummary: 'آگاهسازی از شکایتی که در [[ویکیواژه:تابلوی اعلانات مدیران]] مطرح شدهاست', // localized
defaultSelected: true
},
// let's keep AN and its cousins at the top
/*
'afchd': {
label: 'WP:AFCHD (Articles for creation/Help desk)',
text: '{{subst:AFCHD/u|$SECTION}} ~~~~',
editSummary: 'You have replies at the [[Wikipedia:AFCHD|Articles for Creation Help Desk]]'
},
'coin': {
label: 'WP:COIN (Conflict of interest noticeboard)',
text: '{{subst:Coin-notice|thread=$SECTION}} ~~~~',
editSummary: 'Notice of discussion at [[Wikipedia:Conflict of interest noticeboard]]'
},
*/ // localized [removed]
'drn': {
label: 'وو:تاحا (تابلوی اعلانات حل اختلاف)', // localized
text: '{{subst:آگاهسازی تاحا|بخش=$SECTION}} ~~~~', // localized
editSummary: 'آگاهسازی از بحثی در [[ویکیواژه:تابلوی اعلانات حل اختلاف]]' // localized
}/*,
'effp': {
label: 'WP:EFFP/R (Edit filter false positive report)',
text: '{{EFFPReply|1=$SECTION|2=~~~~}}',
editSummary: 'You have replies to your [[Wikipedia:Edit filter/False positives/Reports|edit filter false positive report]]'
},
'hd': {
label: 'WP:HD (Help desk)',
text: '== Your question at the Help desk ==\n' + '{{helpdeskreply|1=$SECTION|ts=~~~~~}}',
editSummary: 'You have replies at the [[Wikipedia:Help desk|Wikipedia help desk]]'
},
'th': {
label: 'WP:THQ (Teahouse question forum)',
text: "== Teahouse talkback: you've got messages! ==\n{{WP:Teahouse/Teahouse talkback|WP:Teahouse/Questions|$SECTION|ts=~~~~}}",
editSummary: 'You have replies at the [[Wikipedia:Teahouse/Questions|Teahouse question board]]'
},
'otrs': {
label: 'WP:OTRS/N (OTRS noticeboard)',
text: '{{OTRSreply|1=$SECTION|2=~~~~}}',
editSummary: 'You have replies at the [[Wikipedia:OTRS noticeboard|OTRS noticeboard]]'
}
*/ // localized [removed]
};
Twinkle.talkback.evaluate = function(e) {
var form = e.target;
var tbtarget = form.getChecked('tbtarget')[0];
var page, message;
var section = form.section.value;
var editSummary;
if (tbtarget === 'notice') {
page = form.noticeboard.value;
editSummary = Twinkle.talkback.noticeboards[page].editSummary;
} else {
// usertalk, other, see
page = form.page ? form.page.value : mw.config.get('wgUserName');
if (form.message) {
message = form.message.value.trim();
}
if (tbtarget === 'mail') {
editSummary = "اعلان: شما رایانامه دریافت کردید"; // localized
} else if (tbtarget === 'see') {
editSummary = 'لطفا بحث در [[:' + page + (section ? '#' + section : '') + ']] را ببینید'; // localized
} else { // tbtarget one of mytalk, usertalk, other
editSummary = 'بازبحث ([[:'; // localized
if (tbtarget !== 'other' && !/^\s*user talk:/i.test(page)) {
editSummary += 'بحث کاربر:'; // localized
}
editSummary += page + (section ? '#' + section : '') + ']])';
}
}
var text = '\n\n' + Twinkle.talkback.getNoticeWikitext(tbtarget, page, section, message);
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
var fullUserTalkPageName = mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').user_talk] + ':' + mw.config.get('wgRelevantUserName');
Morebits.wiki.actionCompleted.redirect = fullUserTalkPageName;
Morebits.wiki.actionCompleted.notice = 'بازبحث کامل شد؛ بارگیری مجدد صفحه بحث در چند ثانیه'; // localized
var talkpage = new Morebits.wiki.page(fullUserTalkPageName, 'اضافه کردن بازبحث'); // localized
talkpage.setAppendText(text);
talkpage.setEditSummary(editSummary + Twinkle.getPref('summaryAd'));
talkpage.setCreateOption('recreate');
talkpage.setMinorEdit(Twinkle.getPref('markTalkbackAsMinor'));
talkpage.setFollowRedirect(true);
talkpage.append();
};
Twinkle.talkback.preview = function(form) {
var tbtarget = form.getChecked('tbtarget')[0];
var section = form.section.value;
var page, message;
if (tbtarget === 'notice') {
page = form.noticeboard.value;
} else {
// usertalk, other, see
page = form.page ? form.page.value : mw.config.get('wgUserName');
if (form.message) {
message = form.message.value.trim();
}
}
var noticetext = Twinkle.talkback.getNoticeWikitext(tbtarget, page, section, message);
form.previewer.beginRender(noticetext, 'User_talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username
};
Twinkle.talkback.getNoticeWikitext = function(tbtarget, page, section, message) {
var text;
if (tbtarget === 'notice') {
text = Morebits.string.safeReplace(Twinkle.talkback.noticeboards[page].text, '$SECTION', section);
} else if (tbtarget === 'see') {
text = '{{جا:دعوت|مکان=' + page + (section ? '#' + section : '') + '|بیشتر=' + message.trim() + '}}'; // lacalized
} else {
text = '==';
if (tbtarget === 'mail') {
text += Twinkle.getPref('mailHeading') + '==\n' + "{{رایانامه|subject=" + section; // localized
} else { // tbtarget one of mytalk, usertalk, other
// clean talkback heading: strip section header markers that were erroneously suggested in the documentation
text += Twinkle.getPref('talkbackHeading').replace(/^\s*=+\s*(.*?)\s*=+$\s*/, '$1') +
'==\n' + '{{بازبحث|' + page + (section ? '|' + section : ''); // localized
}
text += '|ts=~~~~~}}';
if (message) {
text += '\n' + message + ' ~~~~';
} else if (Twinkle.getPref('insertTalkbackSignature')) {
text += '\n~~~~';
}
}
return text;
};
})(jQuery);
// </nowiki>
/* ===== twinklespeedy.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklespeedy.js: CSD module
****************************************
* Mode of invocation: Tab ("CSD")
* Active on: Non-special, existing pages
*
* NOTE FOR DEVELOPERS:
* If adding a new criterion, add it to the appropriate places at the top of
* twinkleconfig.js. Also check out the default values of the CSD preferences
* in twinkle.js, and add your new criterion to those if you think it would be
* good.
*/
Twinkle.speedy = function twinklespeedy() {
// Disable on:
// * special pages
// * non-existent pages
if (mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId')) {
return;
}
Twinkle.addPortletLink(Twinkle.speedy.callback, 'حذف سریع', 'tw-csd', Morebits.userIsSysop ? 'حذف صفحه طبق وو:محس' : 'درخواست حذف صفحه طبق وو:محس'); // localized
};
// This function is run when the CSD tab/header link is clicked
Twinkle.speedy.callback = function twinklespeedyCallback() {
Twinkle.speedy.initDialog(Morebits.userIsSysop ? Twinkle.speedy.callback.evaluateSysop : Twinkle.speedy.callback.evaluateUser, true);
};
// Used by unlink feature
Twinkle.speedy.dialog = null;
// Used throughout
Twinkle.speedy.hasCSD = !!$('#delete-reason').length;
// The speedy criteria list can be in one of several modes
Twinkle.speedy.mode = {
sysopSingleSubmit: 1, // radio buttons, no subgroups, submit when "Submit" button is clicked
sysopRadioClick: 2, // radio buttons, no subgroups, submit when a radio button is clicked
sysopMultipleSubmit: 3, // check boxes, subgroups, "Submit" button already present
sysopMultipleRadioClick: 4, // check boxes, subgroups, need to add a "Submit" button
userMultipleSubmit: 5, // check boxes, subgroups, "Submit" button already pressent
userMultipleRadioClick: 6, // check boxes, subgroups, need to add a "Submit" button
userSingleSubmit: 7, // radio buttons, subgroups, submit when "Submit" button is clicked
userSingleRadioClick: 8, // radio buttons, subgroups, submit when a radio button is clicked
// are we in "delete page" mode?
// (sysops can access both "delete page" [sysop] and "tag page only" [user] modes)
isSysop: function twinklespeedyModeIsSysop(mode) {
return mode === Twinkle.speedy.mode.sysopSingleSubmit ||
mode === Twinkle.speedy.mode.sysopMultipleSubmit ||
mode === Twinkle.speedy.mode.sysopRadioClick ||
mode === Twinkle.speedy.mode.sysopMultipleRadioClick;
},
// do we have a "Submit" button once the form is created?
hasSubmitButton: function twinklespeedyModeHasSubmitButton(mode) {
return mode === Twinkle.speedy.mode.sysopSingleSubmit ||
mode === Twinkle.speedy.mode.sysopMultipleSubmit ||
mode === Twinkle.speedy.mode.sysopMultipleRadioClick ||
mode === Twinkle.speedy.mode.userMultipleSubmit ||
mode === Twinkle.speedy.mode.userMultipleRadioClick ||
mode === Twinkle.speedy.mode.userSingleSubmit;
},
// is db-multiple the outcome here?
isMultiple: function twinklespeedyModeIsMultiple(mode) {
return mode === Twinkle.speedy.mode.userMultipleSubmit ||
mode === Twinkle.speedy.mode.sysopMultipleSubmit ||
mode === Twinkle.speedy.mode.userMultipleRadioClick ||
mode === Twinkle.speedy.mode.sysopMultipleRadioClick;
}
};
// Prepares the speedy deletion dialog and displays it
Twinkle.speedy.initDialog = function twinklespeedyInitDialog(callbackfunc) {
var dialog;
Twinkle.speedy.dialog = new Morebits.simpleWindow(Twinkle.getPref('speedyWindowWidth'), Twinkle.getPref('speedyWindowHeight'));
dialog = Twinkle.speedy.dialog;
dialog.setTitle('انتخاب معیار حذف سریع'); // localized
dialog.setScriptName('توینکل'); // localized
dialog.addFooterLink('سیاست حذف سریع', 'وو:محس'); // localized
dialog.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#speedy'); // localized
var form = new Morebits.quickForm(callbackfunc, Twinkle.getPref('speedySelectionStyle') === 'radioClick' ? 'change' : null);
if (Morebits.userIsSysop) {
form.append({
type: 'checkbox',
list: [
{
label: 'فقط برچسب بزن، حذف نکن', // localized
value: 'tag_only',
name: 'tag_only',
tooltip: 'اگر فقط قصد دارید به صفحه برچسب بزنید، نه این که آن را حذف سریع کنید، این گزینه را انتخاب کنید.', // localized
checked: !(Twinkle.speedy.hasCSD || Twinkle.getPref('deleteSysopDefaultToDelete')),
event: function(event) {
var cForm = event.target.form;
var cChecked = event.target.checked;
// enable talk page checkbox
if (cForm.talkpage) {
cForm.talkpage.checked = !cChecked && Twinkle.getPref('deleteTalkPageOnDelete');
}
// enable redirects checkbox
cForm.redirects.checked = !cChecked;
// enable delete multiple
cForm.delmultiple.checked = false;
// enable notify checkbox
cForm.notify.checked = cChecked;
// enable deletion notification checkbox
cForm.warnusertalk.checked = !cChecked && !Twinkle.speedy.hasCSD;
// enable multiple
cForm.multiple.checked = false;
// enable requesting creation protection
cForm.salting.checked = false;
Twinkle.speedy.callback.modeChanged(cForm);
event.stopPropagation();
}
}
]
});
var deleteOptions = form.append({
type: 'div',
name: 'delete_options'
});
deleteOptions.append({
type: 'header',
label: 'گزینههای حذف' // localized
});
if (mw.config.get('wgNamespaceNumber') % 2 === 0 && (mw.config.get('wgNamespaceNumber') !== 2 || (/\//).test(mw.config.get('wgTitle')))) { // hide option for user pages, to avoid accidentally deleting user talk page
deleteOptions.append({
type: 'checkbox',
list: [
{
label: 'صفحه بحث را نیز حذف کن', // localized
value: 'talkpage',
name: 'talkpage',
tooltip: "این گزینه صفحهٔ بحث را هم حذف میکند. اگر معیار پ۸ (پروندهٔ موجود در انبار) را انتخاب کنید، این گزینه نادیده گرفته میشود و صفحهٔ بحث حذف نمیشود.", // localized
checked: Twinkle.getPref('deleteTalkPageOnDelete'),
event: function(event) {
event.stopPropagation();
}
}
]
});
}
deleteOptions.append({
type: 'checkbox',
list: [
{
label: 'همچنین حذف تغییرمسیرها', // localized
value: 'redirects',
name: 'redirects',
tooltip: 'این گزینه تمام تغییرمسیرهای ورودی به صفحه را نیز حذف میکند. از این گزینه برای حذفهایی که جهت انتقال یا ادغام صفحه هستند استفاده نکنید.', // localized
checked: Twinkle.getPref('deleteRedirectsOnDelete'),
event: function(event) {
event.stopPropagation();
}
}
]
});
deleteOptions.append({
type: 'checkbox',
list: [
{
label: 'حذف بر اساس چند معیار', // localized
value: 'delmultiple',
name: 'delmultiple',
tooltip: 'اگر این گزینه را انتخاب کنید، میتوانید بیش از یک معیار را به عنوان دلیل حذف صفحه برگزینید. برای مثال، ترکیب معیار ع۱۱ و م۷ برای حذف مقالهها رایج است.', // localized
event: function(event) {
Twinkle.speedy.callback.modeChanged(event.target.form);
event.stopPropagation();
}
}
]
});
deleteOptions.append({
type: 'checkbox',
list: [
{
label: 'آگاه کردن سازندهٔ صفحه از حذف آن', // localized
value: 'warnusertalk',
name: 'warnusertalk',
tooltip: 'یک الگوی آگاهسازی روی صفحهٔ بحث کاربری که صفحه را ایجاد کرده قرار داده میشود، به شرط آن که در ترجیحات توینکل، آگاهسازی را برای این معیار فعال کرده باشید ' + // localized
'و این گزینه را هم تیک زده باشید. ممکن است برای سازندهٔ مقاله پیام خوشامدی هم فرستاده شود.', // localized
checked: !Twinkle.speedy.hasCSD,
event: function(event) {
event.stopPropagation();
}
}
]
});
}
var tagOptions = form.append({
type: 'div',
name: 'tag_options'
});
if (Morebits.userIsSysop) {
tagOptions.append({
type: 'header',
label: 'گزینههای مربوط به برچسبها' // localized
});
}
tagOptions.append({
type: 'checkbox',
list: [
{
label: 'آگاه کردن سازندهٔ صفحه در صورت امکان', // localized
value: 'notify',
name: 'notify',
tooltip: 'یک الگوی آگاهسازی روی صفحهٔ بحث کاربری که صفحه را ایجاد کرده قرار میگیرد، به شرط آن که در ترجیحات توینکل، آگاهسازی را برای این معیار فعال کرده باشید ' + // localized
'و این گزینه را هم تیک بزنید. ممکن است برای سازندهٔ صفحه پیام خوشامدی هم فرستاده شود.', // localized
checked: !Morebits.userIsSysop || !(Twinkle.speedy.hasCSD || Twinkle.getPref('deleteSysopDefaultToDelete')),
event: function(event) {
event.stopPropagation();
}
}
]
});
tagOptions.append({
type: 'checkbox',
list: [
{
label: 'برچسب برای محافظت در برابر ایجاد', // localized
value: 'salting',
name: 'salting',
tooltip: 'اگر این گزینه را انتخاب کنید، کنار برچسب حذف سریع یک برچسب {{نمک}} هم قرار میدهد تا از مدیران درخواست کند که صفحه پس از حذف، در برابر ایجاد هم محافظت شود. فقط در صورتی این گزینه را استفاده کنید که صفحه بارها ایجاد شده باشد.', // localized
event: function(event) {
event.stopPropagation();
}
}
]
});
tagOptions.append({
type: 'checkbox',
list: [
{
label: 'برچسب با چند دلیل', // localized
value: 'multiple',
name: 'multiple',
tooltip: 'اگر این گزینه را انتخاب کنید، میتوانید بیش از یک معیار را به عنوان دلیل حذف صفحه برگزینید. برای مثال، ترکیب معیار ع۱۱ و م۷ برای حذف مقالهها رایج است.', // localized
event: function(event) {
Twinkle.speedy.callback.modeChanged(event.target.form);
event.stopPropagation();
}
}
]
});
form.append({
type: 'div',
name: 'work_area',
label: 'فعال کردن پودمان حذف سریع ناموفق بود. لطفاً دوباره تلاش کنید یا در [[ویکیواژه:فنی]] گزارش کنید.' // localized
});
if (Twinkle.getPref('speedySelectionStyle') !== 'radioClick') {
form.append({ type: 'submit', className: 'tw-speedy-submit' }); // Renamed in modeChanged
}
var result = form.render();
dialog.setContent(result);
dialog.display();
Twinkle.speedy.callback.modeChanged(result);
};
Twinkle.speedy.callback.getMode = function twinklespeedyCallbackGetMode(form) {
var mode = Twinkle.speedy.mode.userSingleSubmit;
if (form.tag_only && !form.tag_only.checked) {
if (form.delmultiple.checked) {
mode = Twinkle.speedy.mode.sysopMultipleSubmit;
} else {
mode = Twinkle.speedy.mode.sysopSingleSubmit;
}
} else {
if (form.multiple.checked) {
mode = Twinkle.speedy.mode.userMultipleSubmit;
} else {
mode = Twinkle.speedy.mode.userSingleSubmit;
}
}
if (Twinkle.getPref('speedySelectionStyle') === 'radioClick') {
mode++;
}
return mode;
};
Twinkle.speedy.callback.modeChanged = function twinklespeedyCallbackModeChanged(form) {
var namespace = mw.config.get('wgNamespaceNumber');
// first figure out what mode we're in
var mode = Twinkle.speedy.callback.getMode(form);
var isSysopMode = Twinkle.speedy.mode.isSysop(mode);
if (isSysopMode) {
$('[name=delete_options]').show();
$('[name=tag_options]').hide();
$('button.tw-speedy-submit').text('حذف صفحه'); // localized
} else {
$('[name=delete_options]').hide();
$('[name=tag_options]').show();
$('button.tw-speedy-submit').text('برچسب زدن به صفحه'); // localized
}
var work_area = new Morebits.quickForm.element({
type: 'div',
name: 'work_area'
});
if (mode === Twinkle.speedy.mode.userMultipleRadioClick || mode === Twinkle.speedy.mode.sysopMultipleRadioClick) {
var evaluateType = isSysopMode ? 'evaluateSysop' : 'evaluateUser';
work_area.append({
type: 'div',
label: 'هنگامی که کارتان تمام شد، کلیک کنید:' // localized
});
work_area.append({
type: 'button',
name: 'submit-multiple',
label: isSysopMode ? 'حذف صفحه' : 'برچسب زدن به صفحه', // localized
event: function(event) {
Twinkle.speedy.callback[evaluateType](event);
event.stopPropagation();
}
});
}
var radioOrCheckbox = Twinkle.speedy.mode.isMultiple(mode) ? 'checkbox' : 'radio';
if (isSysopMode && !Twinkle.speedy.mode.isMultiple(mode)) {
work_area.append({ type: 'header', label: 'دلیل دیگر' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.customRationale, mode) });
}
if (namespace % 2 === 1 && namespace !== 3) {
// show db-talk on talk pages, but not user talk pages
work_area.append({ type: 'header', label: 'صفحههای بحث' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.talkList, mode) });
}
if (!mw.config.get('wgIsRedirect')) {
switch (namespace) {
case 0: // article
case 1: // talk
work_area.append({ type: 'header', label: 'مقالهها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.articleList, mode) });
break;
case 2: // user
case 3: // user talk
work_area.append({ type: 'header', label: 'صفحههای کاربری' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.userList, mode) });
break;
case 6: // file
case 7: // file talk
work_area.append({ type: 'header', label: 'پروندهها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.fileList, mode) });
if (!isSysopMode) {
work_area.append({ type: 'div', label: 'افزودن برچسب برای معیارهای پ۴ (بدون مجوز)، پ۵ (پروندهٔ غیر آزاد استفاده نشده)، پ۶ (فاقد دلیل برای استفادهٔ منصفانه) و پ۱۱ (بدون مدرک برای مجوز) با استفاده از زبانهٔ «تصاویر» در توینکل ممکن است.' }); // localized
}
break;
case 10: // template
case 11: // template talk
work_area.append({ type: 'header', label: 'الگوها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.templateList, mode) });
break;
case 14: // category
case 15: // category talk
work_area.append({ type: 'header', label: 'ردهها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.categoryList, mode) });
break;
case 100: // portal
case 101: // portal talk
work_area.append({ type: 'header', label: 'درگاهها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.portalList, mode) });
break;
default:
break;
}
} else {
if (namespace === 2 || namespace === 3) {
work_area.append({ type: 'header', label: 'صفحههای کاربری' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.userList, mode) });
}
work_area.append({ type: 'header', label: 'تغییرمسیرها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.redirectList, mode) });
}
var generalCriteria = Twinkle.speedy.generalList;
// custom rationale lives under general criteria when tagging
if (!isSysopMode) {
generalCriteria = Twinkle.speedy.customRationale.concat(generalCriteria);
}
work_area.append({ type: 'header', label: 'دلایل عمومی' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(generalCriteria, mode) });
var old_area = Morebits.quickForm.getElements(form, 'work_area')[0];
form.replaceChild(work_area.render(), old_area);
// if sysop, check if CSD is already on the page and fill in custom rationale
if (isSysopMode && Twinkle.speedy.hasCSD) {
var customOption = $('input[name=csd][value=reason]')[0];
if (customOption) {
if (Twinkle.getPref('speedySelectionStyle') !== 'radioClick') {
// force listeners to re-init
customOption.click();
customOption.parentNode.appendChild(customOption.subgroup);
}
customOption.subgroup.querySelector('input').value = decodeURIComponent($('#delete-reason').text()).replace(/\+/g, ' ');
}
}
};
Twinkle.speedy.generateCsdList = function twinklespeedyGenerateCsdList(list, mode) {
// mode switches
var isSysopMode = Twinkle.speedy.mode.isSysop(mode);
var multiple = Twinkle.speedy.mode.isMultiple(mode);
var hasSubmitButton = Twinkle.speedy.mode.hasSubmitButton(mode);
var pageNamespace = mw.config.get('wgNamespaceNumber');
var openSubgroupHandler = function(e) {
$(e.target.form).find('input').prop('disabled', true);
$(e.target.form).children().css('color', 'gray');
$(e.target).parent().css('color', 'black').find('input').prop('disabled', false);
$(e.target).parent().find('input:text')[0].focus();
e.stopPropagation();
};
var submitSubgroupHandler = function(e) {
var evaluateType = Twinkle.speedy.mode.isSysop(mode) ? 'evaluateSysop' : 'evaluateUser';
Twinkle.speedy.callback[evaluateType](e);
e.stopPropagation();
};
return $.map(list, function(critElement) {
var criterion = $.extend({}, critElement);
if (multiple) {
if (criterion.hideWhenMultiple) {
return null;
}
if (criterion.hideSubgroupWhenMultiple) {
criterion.subgroup = null;
}
} else {
if (criterion.hideWhenSingle) {
return null;
}
if (criterion.hideSubgroupWhenSingle) {
criterion.subgroup = null;
}
}
if (isSysopMode) {
if (criterion.hideWhenSysop) {
return null;
}
if (criterion.hideSubgroupWhenSysop) {
criterion.subgroup = null;
}
} else {
if (criterion.hideWhenUser) {
return null;
}
if (criterion.hideSubgroupWhenUser) {
criterion.subgroup = null;
}
}
if (mw.config.get('wgIsRedirect') && criterion.hideWhenRedirect) {
return null;
}
if (criterion.showInNamespaces && criterion.showInNamespaces.indexOf(pageNamespace) < 0) {
return null;
}
if (criterion.hideInNamespaces && criterion.hideInNamespaces.indexOf(pageNamespace) > -1) {
return null;
}
if (criterion.subgroup && !hasSubmitButton) {
if (Array.isArray(criterion.subgroup)) {
criterion.subgroup = criterion.subgroup.concat({
type: 'button',
name: 'submit',
label: isSysopMode ? 'حذف صفحه' : 'برچسب زدن به صفحه', // localized
event: submitSubgroupHandler
});
} else {
criterion.subgroup = [
criterion.subgroup,
{
type: 'button',
name: 'submit', // ends up being called "csd.submit" so this is OK
label: isSysopMode ? 'حذف صفحه' : 'برچسب زدن به صفحه', // localized
event: submitSubgroupHandler
}
];
}
// FIXME: does this do anything?
criterion.event = openSubgroupHandler;
}
return criterion;
});
};
Twinkle.speedy.customRationale = [
{
label: 'دلیل دیگر' + (Morebits.userIsSysop ? ' (دلیل شخصیسازیشده برای حذف)' : ' با استفاده از الگوی {{حذف سریع}}'), // localized
value: 'reason',
tooltip: 'دست کم یکی از معیارهای حذف سریع باید برای صفحه مصداق داشته باشد، و شما این را باید در دلیلی که مینویسید ذکر کنید. این گزینه برای آن نیست که وقتی دلیل حذف را بین گزینههای دیگر نیافتید از آن استفاده کنید.', // localized
subgroup: {
name: 'reason_1',
type: 'input',
label: 'دلیل: ', // localized
size: 60
},
hideWhenMultiple: true
}
];
Twinkle.speedy.talkList = [
{
label: 'بحث یتیم: صفحهٔ بحثی بدون صفحهٔ اصلی',
value: 'talk',
reason: 'بحث یتیم',
tooltip: 'صفحهٔ بحثی که صفحهٔ اصلی متناظر آن وجود ندارد یا حذف شدهاست. شامل بایگانیهای بحث و صفحههای بحث پروندههای موجود در ویکیانبار نمیشود.'
}
];
Twinkle.speedy.fileList = [
{
label: 'تکراری: نسخهٔ یکسان در ویکیواژه یا ویکیانبار موجود است',
value: 'duplicate',
reason: 'تکراری',
tooltip: 'پروندهای که نسخهٔ کاملاً یکسان آن هماکنون در ویکیواژه یا ویکیانبار موجود است.'
},
{
label: 'استفاده نشده: پروندهٔ بیکاربرد',
value: 'unused',
reason: 'استفاده نشده',
tooltip: 'پروندهای که در هیچ صفحهای به کار نرفته و کاربردی برای آن متصور نیست.'
}
];
Twinkle.speedy.articleList = [];
Twinkle.speedy.categoryList = [
{
label: 'استفاده نشده: ردهٔ خالی و بیکاربرد',
value: 'unused',
reason: 'استفاده نشده',
tooltip: 'ردهای که خالی است و کاربردی برای آن متصور نیست.'
}
];
Twinkle.speedy.userList = [];
Twinkle.speedy.templateList = [
{
label: 'استفاده نشده: الگوی بیکاربرد',
value: 'unused',
reason: 'استفاده نشده',
tooltip: 'الگویی که در هیچ صفحهای به کار نرفته و کاربردی برای آن متصور نیست.'
}
];
Twinkle.speedy.portalList = [];
Twinkle.speedy.generalList = [
{
label: 'خرابکاری یا آزمایش محض',
value: 'vandalism',
reason: 'خرابکاری',
tooltip: 'صفحهای که تنها برای خرابکاری، آزمایش یا محتوای بیمعنا ساخته شدهاست.'
},
{
label: 'حذفشده: بازسازی صفحهٔ پیشتر حذفشده',
value: 'deleted',
reason: 'حذف شده',
tooltip: 'صفحهای که پیشتر حذف شده و بی هیچ تغییر معناداری دوباره ساخته شدهاست.'
},
{
label: 'نقض حق تکثیر',
value: 'copyvio',
reason: 'نقض حق تکثیر',
tooltip: 'صفحهای که آشکارا رونوشت از منبعی دارای حق تکثیر است و محتوای آزاد قابلنگهداری ندارد.',
subgroup: {
name: 'copyvio_url',
type: 'input',
label: 'نشانی منبع (اختیاری): ',
size: 60
}
},
{
label: 'تکراری: نسخهٔ یکسان موجود است',
value: 'duplicate',
reason: 'تکراری',
tooltip: 'مدخل یا صفحهای که نسخهٔ یکسان آن هماکنون در ویکیواژه موجود است.',
hideInNamespaces: [ 6, 14, 10 ]
},
{
label: 'منتقلشده به پروژهٔ دیگر ویکیمدیا',
value: 'moved',
reason: 'ویکیمدیا',
tooltip: 'محتوایی که به پروژهٔ دیگری از بنیاد ویکیمدیا منتقل شده و ماندن آن در ویکیواژه لازم نیست.'
}
];
Twinkle.speedy.redirectList = [];
Twinkle.speedy.normalizeHash = {
'reason': 'حذف سریع',
'vandalism': 'خرابکاری',
'deleted': 'حذف شده',
'copyvio': 'نقض حق تکثیر',
'duplicate': 'تکراری',
'unused': 'استفاده نشده',
'moved': 'ویکیمدیا',
'talk': 'بحث یتیم'
};
Twinkle.speedy.callbacks = {
getTemplateCodeAndParams: function(params) {
var reasonFor = function(value, tp) {
if (value === 'reason') {
return (tp && tp['1']) ? tp['1'] : '';
}
var r = Twinkle.speedy.normalizeHash[value] || value;
if (value === 'copyvio' && tp && tp.url) {
r += ' ([' + tp.url + '])';
}
return r;
};
var reasons = [];
$.each(params.values, function(index, value) {
var r = reasonFor(value, params.templateParams[index]);
if (r) {
reasons.push(r);
}
});
if (!reasons.length) {
reasons.push('حذف سریع');
}
var code;
if (reasons.length > 2) {
code = '{{حذف سریع|' + reasons[0] + '|' + reasons.slice(1).join('؛ ') + '}}';
} else {
code = '{{حذف سریع|' + reasons.join('|') + '}}';
}
params.utparams = { reason: reasons.join('؛ ') };
return [code, params.utparams];
},
parseWikitext: function(wikitext, callback) {
var query = {
action: 'parse',
prop: 'text',
pst: 'true',
text: wikitext,
contentmodel: 'wikitext',
title: mw.config.get('wgPageName')
};
var statusIndicator = new Morebits.status('ایجاد خلاصهٔ حذف'); // localized
var api = new Morebits.wiki.api('Parsing deletion template', query, function(apiObj) {
var reason = decodeURIComponent($(apiObj.getXML().querySelector('text').childNodes[0].nodeValue).find('#delete-reason').text()).replace(/\+/g, ' ');
if (!reason) {
statusIndicator.warn('امکان ایجاد خلاصه از الگوی حذف وجود ندارد'); // localized
} else {
statusIndicator.info('کامل'); // localized
}
callback(reason);
}, statusIndicator);
api.post();
},
noteToCreator: function(pageobj) {
var params = pageobj.getCallbackParameters();
var initialContrib = pageobj.getCreator();
// disallow notifying yourself
if (initialContrib === mw.config.get('wgUserName')) {
Morebits.status.warn('شما (' + initialContrib + ') این صفحه را ایجاد کردهاید؛ توقف آگاهسازی کاربر'); // localized
initialContrib = null;
// don't notify users when their user talk page is nominated/deleted
} else if (initialContrib === mw.config.get('wgTitle') && mw.config.get('wgNamespaceNumber') === 3) {
Morebits.status.warn('Notifying initial contributor: this user created their own user talk page; skipping notification');
initialContrib = null;
// quick hack to prevent excessive unwanted notifications, per request. Should actually be configurable on recipient page...
} else if ((initialContrib === 'Cyberbot I' || initialContrib === 'SoxBot') && params.normalizeds[0] === 'f2') {
Morebits.status.warn('Notifying initial contributor: page created procedurally by bot; skipping notification');
initialContrib = null;
// Check for already existing tags
} else if (Twinkle.speedy.hasCSD && params.warnUser && !confirm('The page is has a deletion-related tag, and thus the creator has likely been notified. Do you want to notify them for this deletion as well?')) {
Morebits.status.info('Notifying initial contributor', 'canceled by user; skipping notification.');
initialContrib = null;
}
if (initialContrib) {
var usertalkpage = new Morebits.wiki.page('User talk:' + initialContrib, 'Notifying initial contributor (' + initialContrib + ')'),
notifytext, i, editsummary;
notifytext = '\n{{جا:هشدار حذف سریع|' + Morebits.pageNameNorm + '|' + ((params.utparams && params.utparams.reason) || 'حذف سریع') + '}} ~~~~';
editsummary = 'آگاهسازی: ' + (params.warnUser ? '' : 'نامزدی ') + 'حذف سریع'; // localized
if (params.normalizeds.indexOf('g10') === -1) { // no article name in summary for G10 taggings
editsummary += ' [[:' + Morebits.pageNameNorm + ']].';
} else {
editsummary += ' صفحهٔ حمله.'; // localized
}
usertalkpage.setAppendText(notifytext);
usertalkpage.setEditSummary(editsummary + Twinkle.getPref('summaryAd'));
usertalkpage.setCreateOption('recreate');
usertalkpage.setFollowRedirect(true);
usertalkpage.append(function onNotifySuccess() {
// add this nomination to the user's userspace log, if the user has enabled it
if (params.lognomination) {
Twinkle.speedy.callbacks.user.addToLog(params, initialContrib);
}
}, function onNotifyError() {
// if user could not be notified, log nomination without mentioning that notification was sent
if (params.lognomination) {
Twinkle.speedy.callbacks.user.addToLog(params, null);
}
});
} else if (params.lognomination) {
// log nomination even if the user notification wasn't sent
Twinkle.speedy.callbacks.user.addToLog(params, null);
}
},
sysop: {
main: function(params) {
var reason;
if (!params.normalizeds.length && params.normalizeds[0] === 'db') {
reason = prompt('Enter the deletion summary to use, which will be entered into the deletion log:', '');
Twinkle.speedy.callbacks.sysop.deletePage(reason, params);
} else {
var code = Twinkle.speedy.callbacks.getTemplateCodeAndParams(params)[0];
Twinkle.speedy.callbacks.parseWikitext(code, function(reason) {
if (params.promptForSummary) {
reason = prompt('Enter the deletion summary to use, or press OK to accept the automatically generated one.', reason);
}
Twinkle.speedy.callbacks.sysop.deletePage(reason, params);
});
}
},
deletePage: function(reason, params) {
var thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'حذف صفحه'); // localized
if (reason === null) {
return Morebits.status.error('درخواست دلیل', 'لغو توسط کاربر'); // localized
} else if (!reason || !reason.replace(/^\s*/, '').replace(/\s*$/, '')) {
return Morebits.status.error('درخواست دلیل', "you didn't give one. I don't know... what with admins and their apathetic antics... I give up...");
}
var deleteMain = function(callback) {
thispage.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
thispage.deletePage(function() {
thispage.getStatusElement().info('انجام شد'); // localized
typeof callback === 'function' && callback();
Twinkle.speedy.callbacks.sysop.deleteTalk(params);
});
};
// look up initial contributor. If prompting user for deletion reason, just display a link.
// Otherwise open the talk page directly
if (params.warnUser) {
thispage.setCallbackParameters(params);
thispage.lookupCreation(function(pageobj) {
deleteMain(function() {
Twinkle.speedy.callbacks.noteToCreator(pageobj);
});
});
} else {
deleteMain();
}
},
deleteTalk: function(params) {
// delete talk page
if (params.deleteTalkPage &&
params.normalized !== 'f8' &&
document.getElementById('ca-talk').className !== 'new') {
var talkpage = new Morebits.wiki.page(mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceNumber') + 1] + ':' + mw.config.get('wgTitle'), 'حذف صفحهٔ بحث'); // localized
talkpage.setEditSummary('[[WP:CSD#G8|G8]]: Talk page of deleted page "' + Morebits.pageNameNorm + '"' + Twinkle.getPref('deletionSummaryAd'));
talkpage.deletePage();
// this is ugly, but because of the architecture of wiki.api, it is needed
// (otherwise success/failure messages for the previous action would be suppressed)
window.setTimeout(function() {
Twinkle.speedy.callbacks.sysop.deleteRedirects(params);
}, 1800);
} else {
Twinkle.speedy.callbacks.sysop.deleteRedirects(params);
}
},
deleteRedirects: function(params) {
// delete redirects
if (params.deleteRedirects) {
var query = {
'action': 'query',
'titles': mw.config.get('wgPageName'),
'prop': 'redirects',
'rdlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
var wikipedia_api = new Morebits.wiki.api('getting list of redirects...', query, Twinkle.speedy.callbacks.sysop.deleteRedirectsMain,
new Morebits.status('در حال حذف تغییرمسیرها')); // localized
wikipedia_api.params = params;
wikipedia_api.post();
}
// promote Unlink tool
var $link, $bigtext;
if (mw.config.get('wgNamespaceNumber') === 6 && params.normalized !== 'f8') {
$link = $('<a/>', {
'href': '#',
'text': 'click here to go to the Unlink tool',
'css': { 'fontSize': '130%', 'fontWeight': 'bold' },
'click': function() {
Morebits.wiki.actionCompleted.redirect = null;
Twinkle.speedy.dialog.close();
Twinkle.unlink.callback('Removing usages of and/or links to deleted file ' + Morebits.pageNameNorm);
}
});
$bigtext = $('<span/>', {
'text': 'To orphan backlinks and remove instances of file usage',
'css': { 'fontSize': '130%', 'fontWeight': 'bold' }
});
Morebits.status.info($bigtext[0], $link[0]);
} else if (params.normalized !== 'f8') {
$link = $('<a/>', {
'href': '#',
'text': 'click here to go to the Unlink tool',
'css': { 'fontSize': '130%', 'fontWeight': 'bold' },
'click': function() {
Morebits.wiki.actionCompleted.redirect = null;
Twinkle.speedy.dialog.close();
Twinkle.unlink.callback('Removing links to deleted page ' + Morebits.pageNameNorm);
}
});
$bigtext = $('<span/>', {
'text': 'To orphan backlinks',
'css': { 'fontSize': '130%', 'fontWeight': 'bold' }
});
Morebits.status.info($bigtext[0], $link[0]);
}
},
deleteRedirectsMain: function(apiobj) {
var xmlDoc = apiobj.getXML();
var $snapshot = $(xmlDoc).find('redirects rd');
var total = $snapshot.length;
var statusIndicator = apiobj.statelem;
if (!total) {
statusIndicator.status('هیچ تغییرمسیری پیدا نشد'); // localized
return;
}
statusIndicator.status('0%');
var current = 0;
var onsuccess = function(apiobjInner) {
var now = parseInt(100 * ++current / total, 10) + '%';
statusIndicator.update(now);
apiobjInner.statelem.unlink();
if (current >= total) {
statusIndicator.info(now + ' (کامل شد)'); // localized
Morebits.wiki.removeCheckpoint();
}
};
Morebits.wiki.addCheckpoint();
$snapshot.each(function(key, value) {
var title = $(value).attr('title');
var page = new Morebits.wiki.page(title, 'در حال حذف تغییرمسیر "' + title + '"'); // localized
page.setEditSummary('[[WP:CSD#G8|G8]]: Redirect to deleted page "' + Morebits.pageNameNorm + '"' + Twinkle.getPref('deletionSummaryAd'));
page.deletePage(onsuccess);
});
}
},
user: {
main: function(pageobj) {
var statelem = pageobj.getStatusElement();
// defaults to /doc for lua modules, which may not exist
if (!pageobj.exists() && mw.config.get('wgPageContentModel') !== 'Scribunto') {
statelem.error("It seems that the page doesn't exist; perhaps it has already been deleted");
return;
}
var text = pageobj.getPageText();
var params = pageobj.getCallbackParameters();
statelem.status('در حال بررسی برچسبهای روی صفحه...'); // localized
// check for existing deletion tags
var tag = /(?:\{\{\s*(db|delete|db-.*?|speedy deletion-.*?)(?:\s*\||\s*\}\}))/.exec(text);
// This won't make use of the db-multiple template but it probably should
if (tag && !confirm('The page already has the CSD-related template {{' + tag[1] + '}} on it. Do you want to add another CSD template?')) {
return;
}
var xfd = /\{\{((?:article for deletion|proposed deletion|prod blp|template for discussion)\/dated|[cfm]fd\b)/i.exec(text) || /#invoke:(RfD)/.exec(text);
if (xfd && !confirm('The deletion-related template {{' + xfd[1] + '}} was found on the page. Do you still want to add a CSD template?')) {
return;
}
// given the params, builds the template and also adds the user talk page parameters to the params that were passed in
// returns => [<string> wikitext, <object> utparams]
var buildData = Twinkle.speedy.callbacks.getTemplateCodeAndParams(params),
code = buildData[0];
params.utparams = buildData[1];
// curate/patrol the page
if (Twinkle.getPref('markSpeedyPagesAsPatrolled')) {
pageobj.triage();
}
// Wrap SD template in noinclude tags if we are in template space.
// Won't work with userboxes in userspace, or any other transcluded page outside template space
if (mw.config.get('wgNamespaceNumber') === 10) { // Template:
code = '<noinclude>' + code + '</noinclude>';
}
// Remove tags that become superfluous with this action
text = text.replace(/\{\{\s*([Uu]serspace draft)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/g, '');
if (mw.config.get('wgNamespaceNumber') === 6) {
// remove "move to Commons" tag - deletion-tagged files cannot be moved to Commons
text = text.replace(/\{\{(mtc|(copy |move )?to ?commons|move to wikimedia commons|copy to wikimedia commons)[^}]*\}\}/gi, '');
}
if (params.requestsalt) {
if (params.normalizeds.indexOf('g10') === -1) {
code = code + '\n{{نمک}}'; // localized
} else {
code = '{{نمک}}\n' + code; // localized
}
}
// Generate edit summary for edit
var editsummary;
if (params.normalizeds.length > 1) {
editsummary = 'ثبت درخواست حذف سریع ('; // localized
$.each(params.normalizeds, function(index, norm) {
editsummary += '[[ویکیواژه:محس#' + norm.toUpperCase() + '|محس ' + norm.toUpperCase() + ']], '; // localized
});
editsummary = editsummary.substr(0, editsummary.length - 2); // remove trailing comma
editsummary += ').';
} else if (params.normalizeds[0] === 'db') {
editsummary = 'ثبت درخواست [[ویکیواژه:محس|حذف سریع]] با معیار «' + params.templateParams[0]['1'] + '».'; // localized
} else {
editsummary = 'ثبت درخواست حذف سریع ([[ویکیواژه:محس#' + params.normalizeds[0].toUpperCase() + '|محس ' + params.normalizeds[0].toUpperCase() + ']]).'; // localized
}
// Set the correct value for |ts= parameter in {{db-g13}}
if (params.normalizeds.indexOf('g13') !== -1) {
code = code.replace('$TIMESTAMP', pageobj.getLastEditTime());
}
pageobj.setPageText(code + (params.normalizeds.indexOf('g10') !== -1 ? '' : '\n' + text)); // cause attack pages to be blanked
pageobj.setEditSummary(editsummary + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(params.watch);
if (params.scribunto) {
pageobj.setCreateOption('recreate'); // Module /doc might not exist
if (params.watch) {
// Watch module in addition to /doc subpage
var watch_query = {
action: 'watch',
titles: mw.config.get('wgPageName'),
token: mw.user.tokens.get('watchToken')
};
new Morebits.wiki.api('Adding Module to watchlist', watch_query).post();
}
}
pageobj.save(Twinkle.speedy.callbacks.user.tagComplete);
},
tagComplete: function(pageobj) {
var params = pageobj.getCallbackParameters();
// Notification to first contributor, will also log nomination to the user's userspace log
if (params.usertalk) {
var thispage = new Morebits.wiki.page(Morebits.pageNameNorm);
thispage.setCallbackParameters(params);
thispage.lookupCreation(Twinkle.speedy.callbacks.noteToCreator);
// or, if not notifying, add this nomination to the user's userspace log without the initial contributor's name
} else if (params.lognomination) {
Twinkle.speedy.callbacks.user.addToLog(params, null);
}
},
// note: this code is also invoked from twinkleimage
// the params used are:
// for CSD: params.values, params.normalizeds (note: normalizeds is an array)
// for DI: params.fromDI = true, params.templatename, params.normalized (note: normalized is a string)
addToLog: function(params, initialContrib) {
var usl = new Morebits.userspaceLogger(Twinkle.getPref('speedyLogPageName'));
usl.initialText =
"This is a log of all [[WP:CSD|speedy deletion]] nominations made by this user using [[WP:TW|Twinkle]]'s CSD module.\n\n" +
'If you no longer wish to keep this log, you can turn it off using the [[Wikipedia:Twinkle/Preferences|preferences panel]], and ' +
'nominate this page for speedy deletion under [[WP:CSD#U1|CSD U1]].' +
(Morebits.userIsSysop ? '\n\nThis log does not track outright speedy deletions made using Twinkle.' : '');
var formatParamLog = function(normalize, csdparam, input) {
if ((normalize === 'G4' && csdparam === 'xfd') ||
(normalize === 'G6' && csdparam === 'page') ||
(normalize === 'G6' && csdparam === 'fullvotepage') ||
(normalize === 'G6' && csdparam === 'sourcepage') ||
(normalize === 'A2' && csdparam === 'source') ||
(normalize === 'A10' && csdparam === 'article') ||
(normalize === 'F1' && csdparam === 'filename') ||
(normalize === 'F5' && csdparam === 'replacement')) {
input = '[[:' + input + ']]';
} else if (normalize === 'G5' && csdparam === 'user') {
input = '[[:User:' + input + ']]';
} else if (normalize === 'G12' && csdparam.lastIndexOf('url', 0) === 0 && input.lastIndexOf('http', 0) === 0) {
input = '[' + input + ' ' + input + ']';
} else if (normalize === 'T3' && csdparam === 'template') {
input = '[[:Template:' + input + ']]';
} else if (normalize === 'F8' && csdparam === 'filename') {
input = '[[commons:' + input + ']]';
} else if (normalize === 'P1' && csdparam === 'criterion') {
input = '[[WP:CSD#' + input + ']]';
}
return ' {' + normalize + ' ' + csdparam + ': ' + input + '}';
};
var extraInfo = '';
// If a logged file is deleted but exists on commons, the wikilink will be blue, so provide a link to the log
var fileLogLink = mw.config.get('wgNamespaceNumber') === 6 ? ' ([{{fullurl:Special:Log|page=' + mw.util.wikiUrlencode(mw.config.get('wgPageName')) + '}} log])' : '';
var editsummary = 'Logging speedy deletion nomination';
var appendText = '# [[:' + Morebits.pageNameNorm;
if (params.fromDI) {
appendText += ']]' + fileLogLink + ': DI [[WP:CSD#' + params.normalized.toUpperCase() + '|CSD ' + params.normalized.toUpperCase() + ']] ({{tl|di-' + params.templatename + '}})';
// The params data structure when coming from DI is quite different,
// so this hardcodes the only interesting items worth logging
['reason', 'replacement', 'source'].forEach(function(item) {
if (params[item]) {
extraInfo += formatParamLog(params.normalized.toUpperCase(), item, params[item]);
return false;
}
});
editsummary += ' [[:' + Morebits.pageNameNorm + ']].'; // localized
} else {
if (params.normalizeds.indexOf('g10') === -1) { // no article name in log for G10 taggings
appendText += ']]' + fileLogLink + ': ';
editsummary += ' [[:' + Morebits.pageNameNorm + ']].'; // localized
} else {
appendText += '|این]] صفحهٔ حمله' + fileLogLink + ': '; // localized
editsummary += ' یک صفحهٔ حمله.'; // localized
}
if (params.normalizeds.length > 1) {
appendText += 'چند معیار ('; // localized
$.each(params.normalizeds, function(index, norm) {
appendText += '[[WP:CSD#' + norm.toUpperCase() + '|' + norm.toUpperCase() + ']], ';
});
appendText = appendText.substr(0, appendText.length - 2); // remove trailing comma
appendText += ')';
} else if (params.normalizeds[0] === 'db') {
appendText += '{{tl|db-reason}}';
} else {
appendText += '[[WP:CSD#' + params.normalizeds[0].toUpperCase() + '|CSD ' + params.normalizeds[0].toUpperCase() + ']] ({{tl|db-' + params.values[0] + '}})';
}
// If params is "empty" it will still be full of empty arrays, but ask anyway
if (params.templateParams) {
// Treat custom rationale individually
if (params.normalizeds[0] && params.normalizeds[0] === 'db') {
extraInfo += formatParamLog('Custom', 'rationale', params.templateParams[0]['1']);
} else {
params.templateParams.forEach(function(item, index) {
var keys = Object.keys(item);
if (keys[0] !== undefined && keys[0].length > 0) {
// Second loop required since some items (G12, F9) may have multiple keys
keys.forEach(function(key, keyIndex) {
if (keys[keyIndex] === 'blanked' || keys[keyIndex] === 'ts') {
return true; // Not worth logging
}
extraInfo += formatParamLog(params.normalizeds[index].toUpperCase(), keys[keyIndex], item[key]);
});
}
});
}
}
}
if (params.requestsalt) {
appendText += '; حفاظت در برابر ایجاد درخواست شدهاست ([[ویکیواژه:نمک|نمکخورده]])'; // localized
}
if (extraInfo) {
appendText += '؛ اطلاعات اضافی:' + extraInfo; // localized
}
if (initialContrib) {
appendText += '؛ {{user|1=' + initialContrib + '}} آگاه شد'; // localized
}
appendText += ' ~~~~~\n';
usl.log(appendText, editsummary + Twinkle.getPref('summaryAd'));
}
}
};
// validate subgroups in the form passed into the speedy deletion tag
Twinkle.speedy.getParameters = function twinklespeedyGetParameters(form, values) {
var parameters = [];
$.each(values, function(index, value) {
var currentParams = [];
switch (value) {
case 'reason':
if (form['csd.reason_1']) {
var dbrationale = form['csd.reason_1'].value;
if (!dbrationale || !dbrationale.trim()) {
alert('دلیل دلخواه: لطفاً دلیلی ارائه کنید.'); // localized
parameters = null;
return false;
}
currentParams['1'] = dbrationale;
}
break;
case 'userreq': // U1
if (form['csd.userreq_rationale']) {
var u1rationale = form['csd.userreq_rationale'].value;
if (mw.config.get('wgNamespaceNumber') === 3 && !(/\//).test(mw.config.get('wgTitle')) &&
(!u1rationale || !u1rationale.trim())) {
alert('CSD U1: Please specify a rationale when nominating user talk pages.');
parameters = null;
return false;
}
currentParams.rationale = u1rationale;
}
break;
case 'repost': // G4
if (form['csd.repost_xfd']) {
var deldisc = form['csd.repost_xfd'].value;
if (deldisc) {
if (!/^(?:وپ|ویکیپدیا):/i.test(deldisc)) { // localized
alert('محس ع۴: صفحه نظرخواهی حذف باید با "ویکیواژه:" آغاز شود.'); // localized
parameters = null;
return false;
}
currentParams.xfd = deldisc;
}
}
break;
case 'banned': // G5
if (form['csd.banned_user'] && form['csd.banned_user'].value) {
currentParams.user = form['csd.banned_user'].value.replace(/^\s*کاربر:/i, ''); // localized
}
break;
case 'move': // G6
if (form['csd.move_page'] && form['csd.move_reason']) {
var movepage = form['csd.move_page'].value,
movereason = form['csd.move_reason'].value;
if (!movepage || !movepage.trim()) {
alert('محس ع۶ (انتقال): لطفاً نام صفحهای که باید به اینجا انتقال یابد مشخص کنید.'); // localized
parameters = null;
return false;
}
if (!movereason || !movereason.trim()) {
alert('محس ع۶ (انتقال): لطفاً دلیل انتقال را مشخص کنید.'); // localized
parameters = null;
return false;
}
currentParams.page = movepage;
currentParams.reason = movereason;
}
break;
case 'xfd': // G6
if (form['csd.xfd_fullvotepage']) {
var xfd = form['csd.xfd_fullvotepage'].value;
if (xfd) {
if (!/^(?:wp|wikipedia):/i.test(xfd)) {
alert('CSD G6 (XFD): The deletion discussion page name, if provided, must start with "Wikipedia:".');
parameters = null;
return false;
}
currentParams.fullvotepage = xfd;
}
}
break;
case 'copypaste': // G6
if (form['csd.copypaste_sourcepage']) {
var copypaste = form['csd.copypaste_sourcepage'].value;
if (!copypaste || !copypaste.trim()) {
alert('محس ع۶ (کپیپیست): لطفاً نام صفحهٔ مبدأ را مشخص کنید.'); // localized
parameters = null;
return false;
}
currentParams.sourcepage = copypaste;
}
break;
case 'g6': // G6
if (form['csd.g6_rationale'] && form['csd.g6_rationale'].value) {
currentParams.rationale = form['csd.g6_rationale'].value;
}
break;
case 'author': // G7
if (form['csd.author_rationale'] && form['csd.author_rationale'].value) {
currentParams.rationale = form['csd.author_rationale'].value;
}
break;
case 'g8': // G8
if (form['csd.g8_rationale'] && form['csd.g8_rationale'].value) {
currentParams.rationale = form['csd.g8_rationale'].value;
}
break;
case 'attack': // G10
currentParams.blanked = 'yes';
// it is actually blanked elsewhere in code, but setting the flag here
break;
case 'copyvio': // G12
if (form['csd.copyvio_url'] && form['csd.copyvio_url'].value) {
currentParams.url = form['csd.copyvio_url'].value;
}
if (form['csd.copyvio_url2'] && form['csd.copyvio_url2'].value) {
currentParams.url2 = form['csd.copyvio_url2'].value;
}
if (form['csd.copyvio_url3'] && form['csd.copyvio_url3'].value) {
currentParams.url3 = form['csd.copyvio_url3'].value;
}
break;
case 'afc': // G13
currentParams.ts = '$TIMESTAMP'; // to be replaced by the last revision timestamp when page is saved
break;
case 'redundantimage': // F1
if (form['csd.redundantimage_filename']) {
var redimage = form['csd.redundantimage_filename'].value;
if (!redimage || !redimage.trim()) {
alert('CSD F1: Please specify the filename of the other file.');
parameters = null;
return false;
}
currentParams.filename = /^\s*(Image|File):/i.test(redimage) ? redimage : 'File:' + redimage;
}
break;
case 'badfairuse': // F7
if (form['csd.badfairuse_rationale'] && form['csd.badfairuse_rationale'].value) {
currentParams.rationale = form['csd.badfairuse_rationale'].value;
}
break;
case 'commons': // F8
if (form['csd.commons_filename']) {
var filename = form['csd.commons_filename'].value;
if (filename && filename.trim() && filename !== Morebits.pageNameNorm) {
currentParams.filename = /^\s*(Image|File):/i.test(filename) ? filename : 'File:' + filename;
}
}
break;
case 'imgcopyvio': // F9
if (form['csd.imgcopyvio_url'] && form['csd.imgcopyvio_rationale']) {
var f9url = form['csd.imgcopyvio_url'].value;
var f9rationale = form['csd.imgcopyvio_rationale'].value;
if ((!f9url || !f9url.trim()) && (!f9rationale || !f9rationale.trim())) {
alert('CSD F9: You must enter a url or reason (or both) when nominating a file under F9.');
parameters = null;
return false;
}
if (form['csd.imgcopyvio_url'].value) {
currentParams.url = f9url;
}
if (form['csd.imgcopyvio_rationale'].value) {
currentParams.rationale = f9rationale;
}
}
break;
case 'foreign': // A2
if (form['csd.foreign_source']) {
var foreignlink = form['csd.foreign_source'].value;
if (!foreignlink || !foreignlink.trim()) {
alert('CSD A2: Please specify an interwiki link to the article of which this is a copy.');
parameters = null;
return false;
}
currentParams.source = foreignlink;
}
break;
case 'transwiki': // A5
if (form['csd.transwiki_location'] && form['csd.transwiki_location'].value) {
currentParams.location = form['csd.transwiki_location'].value;
}
break;
case 'a10': // A10
if (form['csd.a10_article']) {
var duptitle = form['csd.a10_article'].value;
if (!duptitle || !duptitle.trim()) {
alert('محس م۱۰: لطفاً نام مقالهٔ تکرار شده را مشخص کنید.'); // localized
parameters = null;
return false;
}
currentParams.article = duptitle;
}
break;
case 'policy': // T2
if (form['csd.policy_rationale'] && form['csd.policy_rationale'].value) {
currentParams.rationale = form['csd.policy_rationale'].value;
}
break;
case 'duplicatetemplate': // T3
if (form['csd.duplicatetemplate_2']) {
var t3template = form['csd.duplicatetemplate_2'].value;
if (!t3template || !t3template.trim()) {
alert('محس ال۳: لطفاً نام الگوی تکرار شده را مشخص کنید.'); // localized
parameters = null;
return false;
}
currentParams.ts = '~~~~~';
currentParams.template = t3template.replace(/^\s*Template:/i, '');
}
break;
case 'p1': // P1
if (form['csd.p1_criterion']) {
var criterion = form['csd.p1_criterion'].value;
if (!criterion || !criterion.trim()) {
alert('CSD P1: Please specify a single criterion.');
parameters = null;
return false;
}
currentParams.criterion = criterion;
}
break;
default:
break;
}
parameters.push(currentParams);
});
return parameters;
};
// Function for processing talk page notification template parameters
// key1/value1: for {{db-criterion-[notice|deleted]}} (via {{db-csd-[notice|deleted]-custom}})
// utparams.param: for {{db-[notice|deleted]-multiple}}
Twinkle.speedy.getUserTalkParameters = function twinklespeedyGetUserTalkParameters(normalized, parameters) {
var utparams = [];
// Special cases
if (normalized === 'db') {
utparams['2'] = parameters['1'];
} else if (normalized === 'g6') {
utparams.key1 = 'to';
utparams.value1 = Morebits.pageNameNorm;
} else if (normalized === 'g12') {
['url', 'url2', 'url3'].forEach(function(item, idx) {
if (parameters[item]) {
idx++;
utparams['key' + idx] = item;
utparams['value' + idx] = utparams[item] = parameters[item];
}
});
} else {
// Handle the rest
var param;
switch (normalized) {
case 'g4':
param = 'xfd';
break;
case 'a2':
param = 'source';
break;
case 'a5':
param = 'location';
break;
case 'a10':
param = 'article';
break;
case 'f9':
param = 'url';
break;
case 'p1':
param = 'criterion';
break;
default:
break;
}
// No harm in providing a usertalk template with the others' parameters
if (param && parameters[param]) {
utparams.key1 = param;
utparams.value1 = utparams[param] = parameters[param];
}
}
return utparams;
};
Twinkle.speedy.resolveCsdValues = function twinklespeedyResolveCsdValues(e) {
var values = (e.target.form ? e.target.form : e.target).getChecked('csd');
if (values.length === 0) {
alert('Please select a criterion!');
return null;
}
return values;
};
Twinkle.speedy.callback.evaluateSysop = function twinklespeedyCallbackEvaluateSysop(e) {
var form = e.target.form ? e.target.form : e.target;
if (e.target.type === 'checkbox' || e.target.type === 'text' ||
e.target.type === 'select') {
return;
}
var tag_only = form.tag_only;
if (tag_only && tag_only.checked) {
Twinkle.speedy.callback.evaluateUser(e);
return;
}
var values = Twinkle.speedy.resolveCsdValues(e);
if (!values) {
return;
}
var templateParams = Twinkle.speedy.getParameters(form, values);
if (!templateParams) {
return;
}
var normalizeds = values.map(function(value) {
return Twinkle.speedy.normalizeHash[value];
});
// analyse each criterion to determine whether to watch the page, prompt for summary, or notify the creator
var watchPage, promptForSummary;
normalizeds.forEach(function(norm) {
if (Twinkle.getPref('watchSpeedyPages').indexOf(norm) !== -1) {
watchPage = true;
}
if (Twinkle.getPref('promptForSpeedyDeletionSummary').indexOf(norm) !== -1) {
promptForSummary = true;
}
});
var warnusertalk = false;
if (form.warnusertalk.checked) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('warnUserOnSpeedyDelete').indexOf(norm) !== -1) {
if (norm === 'g6' && values[index] !== 'copypaste') {
return true;
}
warnusertalk = true;
return false; // break
}
});
}
var welcomeuser = false;
if (warnusertalk) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('welcomeUserOnSpeedyDeletionNotification').indexOf(norm) !== -1) {
welcomeuser = true;
return false; // break
}
});
}
var params = {
values: values,
normalizeds: normalizeds,
watch: watchPage,
deleteTalkPage: form.talkpage && form.talkpage.checked,
deleteRedirects: form.redirects.checked,
warnUser: warnusertalk,
welcomeuser: welcomeuser,
promptForSummary: promptForSummary,
templateParams: templateParams
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Twinkle.speedy.callbacks.sysop.main(params);
};
Twinkle.speedy.callback.evaluateUser = function twinklespeedyCallbackEvaluateUser(e) {
var form = e.target.form ? e.target.form : e.target;
if (e.target.type === 'checkbox' || e.target.type === 'text' ||
e.target.type === 'select') {
return;
}
var values = Twinkle.speedy.resolveCsdValues(e);
if (!values) {
return;
}
var templateParams = Twinkle.speedy.getParameters(form, values);
if (!templateParams) {
return;
}
// var multiple = form.multiple.checked;
var normalizeds = [];
$.each(values, function(index, value) {
var norm = Twinkle.speedy.normalizeHash[value];
normalizeds.push(norm);
});
// analyse each criterion to determine whether to watch the page/notify the creator
var watchPage = false;
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('watchSpeedyPages').indexOf(norm) !== -1) {
watchPage = true;
return false; // break
}
});
var notifyuser = false;
if (form.notify.checked) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('notifyUserOnSpeedyDeletionNomination').indexOf(norm) !== -1) {
if (norm === 'g6' && values[index] !== 'copypaste') {
return true;
}
notifyuser = true;
return false; // break
}
});
}
var welcomeuser = false;
if (notifyuser) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('welcomeUserOnSpeedyDeletionNotification').indexOf(norm) !== -1) {
welcomeuser = true;
return false; // break
}
});
}
var csdlog = false;
if (Twinkle.getPref('logSpeedyNominations')) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('noLogOnSpeedyNomination').indexOf(norm) === -1) {
csdlog = true;
return false; // break
}
});
}
var params = {
values: values,
normalizeds: normalizeds,
watch: watchPage,
usertalk: notifyuser,
welcomeuser: welcomeuser,
lognomination: csdlog,
requestsalt: form.salting.checked,
templateParams: templateParams
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'Tagging complete';
// Modules can't be tagged, follow standard at TfD and place on /doc subpage
params.scribunto = mw.config.get('wgPageContentModel') === 'Scribunto';
var wikipedia_page = params.scribunto ? new Morebits.wiki.page(mw.config.get('wgPageName') + '/doc', 'Tagging module documentation page') : new Morebits.wiki.page(mw.config.get('wgPageName'), 'Tagging page');
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.speedy.callbacks.user.main);
};
})(jQuery);
// </nowiki>
/* ===== twinkleprod.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinkleprod.js: PROD module
****************************************
* Mode of invocation: Tab ("PROD")
* Active on: Existing articles, files, books which are not redirects,
* and user pages in [[:Category:Wikipedia books (user books)]]
*/
Twinkle.prod = function twinkleprod() {
if (([0, 6].indexOf(mw.config.get('wgNamespaceNumber')) === -1)
|| !mw.config.get('wgCurRevisionId') || Morebits.wiki.isPageRedirect()) {
return;
}
Twinkle.addPortletLink(Twinkle.prod.callback, 'پیشنهاد حذف', 'tw-prod', 'پیشنهاد حذف طبق وو:پح'); // localized
};
// Used in edit summaries, for comparisons, etc.
var namespace;
Twinkle.prod.callback = function twinkleprodCallback() {
Twinkle.prod.defaultReason = Twinkle.getPref('prodReasonDefault');
switch (mw.config.get('wgNamespaceNumber')) {
case 0:
namespace = 'مقاله'; // localized
break;
case 6:
namespace = 'پرونده'; // localized
break;
}
var Window = new Morebits.simpleWindow(800, 410);
Window.setTitle('پیشنهاد حذف'); // localized
Window.setScriptName('توینکل'); // localiezd
var form = new Morebits.quickForm(Twinkle.prod.callback.evaluate);
Window.addFooterLink('سیاست پیشنهاد حذف', 'وو:پح'); // localized
// prod reason controls
var field = form.append({
type: 'field',
label: 'پارامترها', // localized
name: 'parameters'
});
field.append({
type: 'checkbox',
list: [
{
label: 'آگاه کردن سازندهٔ صفحه در صورت امکان', // localized
value: 'notify',
name: 'notify',
tooltip: "در صورت انتخاب این گزینه در صفحه بحث نویسنده مقاله، پیام اطلاعرسانی درج میشود", // localized
checked: true
}
]
});
field.append({
type: 'textarea',
name: 'reason',
label: 'دلیل برای پیشنهاد حذف:', // localized
value: Twinkle.prod.defaultReason
});
Window.addFooterLink('راهنمای توینکل', 'وو:توینکل/توضیحات'); // localized
form.append({ type: 'submit', label: 'پیشنهاد حذف' }); // localized
var result = form.render();
Window.setContent(result);
Window.display();
};
Twinkle.prod.callbacks = {
checkpriors: function(apiobj) {
var params = apiobj.params;
var ts = new Morebits.wiki.page(mw.config.get('wgPageName'));
ts.setFollowRedirect(true); // for NPP, and also because redirects are ineligible for PROD
ts.setCallbackParameters(params);
ts.setLookupNonRedirectCreator(true); // Look for author of first non-redirect revision
ts.lookupCreation(Twinkle.prod.callbacks.creationInfo);
},
creationInfo: function(pageobj) {
var params = pageobj.getCallbackParameters();
params.initialContrib = pageobj.getCreator();
params.creation = pageobj.getCreationTimestamp();
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'برچسب زدن تمام شد'; // localized
var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'برچسب زدن به صفحه'); // localized
wikipedia_page.setFollowRedirect(true); // for NPP, and also because redirects are ineligible for PROD
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.prod.callbacks.main);
},
main: function(pageobj) {
var statelem = pageobj.getStatusElement();
if (!pageobj.exists()) {
statelem.error("به نظر میآید که صفحه وجود ندارد. احتمالا قبلا حذف شده است."); // localized
return;
}
var text = pageobj.getPageText();
var params = pageobj.getCallbackParameters();
// Check for already existing deletion tags
var tag_re = /{{(?:db-?|delete|حذف سریع|حذف زماندار|پیشنهاد حذف|نظرخواهی برای حذف|article for deletion\/dated|ffd\b)|#invoke:RfD/i; // localized
if (tag_re.test(text)) {
statelem.warn('صفحه در حال حاضر برچسب حذف دارد. نامزدی پیشنهاد حذف متوقف شد'); // localized
return;
}
// Remove tags that become superfluous with this action
text = text.replace(/{{\s*(userspace draft|پیشنویس صفحه کاربری|mtc|(copy|move) to wikimedia commons|انتقال به (انبار|ویکیانبار)|(copy |move )?to ?commons)\s*(\|(?:{{[^{}]*}}|[^{}])*)?}}\s*/gi, '');
var prod_re = /{{\s*پیشنهاد حذف\s*[|}]/i; // localized
var summaryText;
if (!prod_re.test(text)) {
// Notification to first contributor
if (params.usertalk) {
// Disallow warning yourself
if (params.initialContrib === mw.config.get('wgUserName')) {
statelem.warn('خود شما (' + params.initialContrib + ') را ایجاد کردهاید؛ توقف اطلاعرسانی به کاربر'); // localized
if (Twinkle.getPref('logProdPages')) {
Twinkle.prod.callbacks.addToLog(params);
}
} else {
var notifytext = '\n== پیشنهاد حذف [[:' + Morebits.pageNameNorm + ']] ==\n' +
'صفحهٔ [[:' + Morebits.pageNameNorm + ']] برای حذف پیشنهاد شدهاست. دلیل: ' + params.reason + ' لطفاً اگر مخالفید نظرتان را بیان کنید. ~~~~\n'; // localized
var usertalkpage = new Morebits.wiki.page('بحث کاربر:' + params.initialContrib, 'اطلاعرسانی به سازنده صفحه (' + params.initialContrib + ')'); // localized
usertalkpage.setAppendText(notifytext);
usertalkpage.setEditSummary('اطلاعرسانی: پیشنهاد حذف [[:' + Morebits.pageNameNorm + ']].' + Twinkle.getPref('summaryAd')); // localized
usertalkpage.setCreateOption('recreate');
usertalkpage.setFollowRedirect(true);
usertalkpage.setCallbackParameters(params);
usertalkpage.append(function onNotifySuccess() {
// add nomination to the userspace log, if the user has enabled it
if (Twinkle.getPref('logProdPages')) {
params.logInitialContrib = params.initialContrib;
Twinkle.prod.callbacks.addToLog(params);
}
}, function onNotifyError() {
// if user could not be notified, log nomination without mentioning that notification was sent
if (Twinkle.getPref('logProdPages')) {
Twinkle.prod.callbacks.addToLog(params);
}
});
}
} else if (Twinkle.getPref('logProdPages')) { // If not notifying, log this PROD
Twinkle.prod.callbacks.addToLog(params);
}
summaryText = 'نامزد کردن ' + namespace + ' برای پیشنهاد حذف طبق [[وو:پح]].'; // localized
text = '{{پیشنهاد حذف|1=' + Morebits.string.formatReasonText(params.reason) + '}}\n' + text; // localized
}
// curate/patrol the page
if (Twinkle.getPref('markProdPagesAsPatrolled')) {
pageobj.triage();
}
pageobj.setPageText(text);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchProdPages'));
pageobj.setCreateOption('nocreate');
pageobj.save();
},
addToLog: function(params) {
var usl = new Morebits.userspaceLogger(Twinkle.getPref('prodLogPageName'));
usl.initialText =
"این صفحه فهرستی از مواردی که این کاربر با استفاده از [[وو:توینکل|توینکل]] برای [[وو:پح|پیشنهاد حذف]] نامزد کردهاست را نشان میدهد.\n\n" + // localized
'اگر نمیخواهید موارد جدید به این سیاهه اضافه شوند، آن را از طریق [[وو:توینکل تر|ترجیحات توینکل]] غیرفعال کنید' + // localized
'و اگر میخواهید سیاهه کلاً حذف شود آن را بر اساس معیار [[وو:محس#ک۱|ک۱]] نامزد حذف سریع کنید.\n'; // localized
var logText = '# [[:' + Morebits.pageNameNorm + ']]';
var summaryText;
// If a logged file is deleted but exists on commons, the wikilink will be blue, so provide a link to the log
logText += namespace === 'پرونده' ? ' ([{{fullurl:Special:Log|page=' + mw.util.wikiUrlencode(mw.config.get('wgPageName')) + '}} سیاهه]): ' : ': '; // localized
logText += 'پیشنهاد حذف'; // localized
if (params.logInitialContrib) {
logText += '; {{کاربر|' + params.logInitialContrib + '}} مطلع شد'; // localized
}
logText += ' ~~~~~\n';
logText += "#* '''دلیل''': " + params.reason + '\n'; // localized
summaryText = 'ثبت نامزدی [[:' + Morebits.pageNameNorm + ']] در سیاههٔ کاربری شما.'; // localized
usl.log(logText, summaryText + Twinkle.getPref('summaryAd'));
}
};
Twinkle.prod.callback.evaluate = function twinkleprodCallbackEvaluate(e) {
var form = e.target;
var params = {
usertalk: form.notify.checked,
reason: form.reason.value
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Twinkle.prod.callbacks.checkpriors({'params': params});
};
})(jQuery);
// </nowiki>
/* ===== twinkledeprod.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinkledeprod.js: Batch deletion of expired PRODs (sysops only)
****************************************
* Mode of invocation: Tab ("Deprod")
* Active on: Categories whose name contains "proposed_deletion"
*/
Twinkle.deprod = function() {
if (
!Morebits.userIsSysop ||
mw.config.get('wgNamespaceNumber') !== 14 ||
!(/پیشنهاد.?حذف/i).test(mw.config.get('wgPageName')) // localized
) {
return;
}
Twinkle.addPortletLink(Twinkle.deprod.callback, 'حذف پیشنهادهای حذف دستهجمعی', 'tw-deprod', 'صفحههای ایجاد شده در این رده را حذف کنید'); // localized
};
var concerns = {};
Twinkle.deprod.callback = function() {
var Window = new Morebits.simpleWindow(800, 400);
Window.setTitle('انجام حذف پیشنهادهای حذف'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('پیشنهاد حذف', 'وو:پح'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#deprod'); // localized
var form = new Morebits.quickForm(callback_commit);
var statusdiv = document.createElement('div');
statusdiv.style.padding = '15px'; // just so it doesn't look broken
Window.setContent(statusdiv);
Morebits.status.init(statusdiv);
Window.display();
var query = {
'action': 'query',
'generator': 'categorymembers',
'gcmtitle': mw.config.get('wgPageName'),
'gcmlimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
'gcmnamespace': '0|6|108|2', // mostly to ignore categories
'prop': [ 'info', 'revisions' ],
'rvprop': [ 'content' ],
'inprop': [ 'protection' ]
};
var statelem = new Morebits.status('گرفتن فهرست صفحهها'); // localized
var wikipedia_api = new Morebits.wiki.api('در حال بارگیری...', query, function(apiobj) { // localized
var $doc = $(apiobj.responseXML);
var $pages = $doc.find('page[ns!="6"]'); // all non-files
var list = [];
var re = /\{\{\s*پیشنهاد حذف/; // localized
$pages.each(function() {
var $page = $(this);
var title = $page.attr('title');
var content = $page.find('revisions rev').text();
var $editprot = $page.find('pr[type="edit"][level="sysop"]');
var isProtected = $editprot.length > 0;
var metadata = [];
var res = re.exec(content);
if (res) {
var parsed = Morebits.wikitext.template.parse(content, res.index);
concerns[title] = parsed.parameters['1'] || parsed.parameters['اهمیت'] || ''; // localized
metadata.push(concerns[title]);
}
if (isProtected) {
metadata.push('محافظت کامل' + // localized
($editprot.attr('expiry') === 'infinity' ? ' بیپایان' : '، انقضا در ' + $editprot.attr('expiry'))); // localized
}
list.push({
label: metadata.length ? '(' + metadata.join('; ') + ')' : '',
value: title,
checked: concerns[title] !== '',
style: isProtected ? 'color:red' : ''
});
});
apiobj.params.form.append({ type: 'header', label: 'صفحهها برای حذف' }); // localized
apiobj.params.form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', true);
}
});
apiobj.params.form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', false);
}
});
apiobj.params.form.append({
'type': 'checkbox',
'name': 'pages',
'list': list
});
apiobj.params.form.append({
'type': 'submit'
});
var rendered = apiobj.params.form.render();
apiobj.params.Window.setContent(rendered);
$(Morebits.quickForm.getElements(rendered, 'pages')).each(function(index, checkbox) {
var $checkbox = $(checkbox);
var link = Morebits.htmlNode('a', $checkbox.val());
link.setAttribute('class', 'deprod-page-link');
link.setAttribute('href', mw.util.getUrl($checkbox.val()));
link.setAttribute('target', '_blank');
$checkbox.next().prepend([link, ' ']);
});
}, statelem);
wikipedia_api.params = { form: form, Window: Window };
wikipedia_api.post();
};
var callback_commit = function(event) {
var pages = event.target.getChecked('pages');
Morebits.status.init(event.target);
var batchOperation = new Morebits.batchOperation('حذف صفحهها'); // localized
batchOperation.setOption('chunkSize', Twinkle.getPref('proddeleteChunks'));
batchOperation.setOption('preserveIndividualStatusLines', true);
batchOperation.setPageList(pages);
batchOperation.run(function(pageName) {
var params = { page: pageName, reason: concerns[page] };
var query = {
'action': 'query',
'titles': pageName,
'prop': 'redirects',
'rdlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
var wikipedia_api = new Morebits.wiki.api('دریافت تغییرمسیرها', query, callback_deleteRedirects); // localized
wikipedia_api.params = params;
wikipedia_api.post();
query = {
'action': 'query',
'titles': 'بحث:' + pageName
};
wikipedia_api = new Morebits.wiki.api('بررسی این که ' + pageName + ' صفحهٔ بحث دارد یا نه', query, // localized
callback_deleteTalk);
wikipedia_api.params = params;
wikipedia_api.post();
var page = new Morebits.wiki.page(pageName, 'در حال حذف صفحه ' + pageName); // localized
page.setEditSummary('حذف طبق [[ویکیواژه:پیشنهاد حذف|پیشنهاد حذف]]، دلیل صفحه: ' + (concerns[pageName] === undefined ? 'نامشخص' : concerns[pageName]) + Twinkle.getPref('deletionSummaryAd')); // localized
page.suppressProtectWarning();
page.deletePage(batchOperation.workerSuccess, batchOperation.workerFailure);
});
},
callback_deleteTalk = function(apiobj) {
var $doc = $(apiobj.responseXML);
var exists = $doc.find('page:not([missing])').length > 0;
if (!exists) {
// no talk page; forget about it
return;
}
var page = new Morebits.wiki.page('Talk:' + apiobj.params.page, 'حذف صفحهٔ بحث ' + apiobj.params.page); // localized
page.setEditSummary('[[وو:محس#ع۸|ع۸]]: [[راهنما:صفحه بحث|صفحهٔ بحث]] صفحهٔ حذف شده «' + apiobj.params.page + '»' + Twinkle.getPref('deletionSummaryAd')); // localized
page.deletePage();
},
callback_deleteRedirects = function(apiobj) {
var $doc = $(apiobj.responseXML);
$doc.find('redirects rd').each(function() {
var title = $(this).attr('title');
var page = new Morebits.wiki.page(title, 'حذف تغییر مسیر صفحه ' + title); // localized
page.setEditSummary('[[وو:محس#ع۸|ع۸]]: تغییر مسیر به صفحهٔ حذف شده «' + apiobj.params.page + '»' + Twinkle.getPref('deletionSummaryAd')); // localized
page.deletePage();
});
};
})(jQuery);
// </nowiki>
/* ===== twinkleprotect.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinkleprotect.js: Protect/RPP module
****************************************
* Mode of invocation: Tab ("PP"/"RPP")
* Active on: Non-special, non-MediaWiki pages
*/
// Note: a lot of code in this module is re-used/called by batchprotect.
Twinkle.protect = function twinkleprotect() {
if (mw.config.get('wgNamespaceNumber') < 0 || mw.config.get('wgNamespaceNumber') === 8) {
return;
}
Twinkle.addPortletLink(Twinkle.protect.callback, Morebits.userIsSysop ? 'حفاظت' : 'حفاظت', 'tw-rpp', // localized
Morebits.userIsSysop ? 'حفاظت صفحه' : 'درخواست حفاظت صفحه'); // localized
};
Twinkle.protect.callback = function twinkleprotectCallback() {
var Window = new Morebits.simpleWindow(620, 530);
Window.setTitle(Morebits.userIsSysop ? 'اعمال، درخواست یا برچسبگذاری حفاظت صفحه' : 'درخواست یا برچسبگذاری حفاظت صفحه'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('الگوهای حفاظت', 'الگو:الگوهای حفاظت'); // localized
Window.addFooterLink('سیاست حفاظت', 'وو:حفاظت'); // localized
Window.addFooterLink('راهنمای توینکل', 'وو:تل/توضیحات#حفاظت'); // localized
var form = new Morebits.quickForm(Twinkle.protect.callback.evaluate);
var actionfield = form.append({
type: 'field',
label: 'نوع کنش' // localized
});
if (Morebits.userIsSysop) {
actionfield.append({
type: 'radio',
name: 'actiontype',
event: Twinkle.protect.callback.changeAction,
list: [
{
label: 'محافظت صفحه', // localized
value: 'protect',
tooltip: 'اجرای حفاظت بر روی صفحه.', // localized
checked: true
}
]
});
}
actionfield.append({
type: 'radio',
name: 'actiontype',
event: Twinkle.protect.callback.changeAction,
list: [
{
label: 'درخواست حفاظت صفحه', // localized
value: 'request',
tooltip: 'اگر می خواهید ' + (Morebits.userIsSysop ? ' بجای اینکه خودتان شخصا محافظت را اجرا کنید،' : 'از طریق وو:دمص درخواست دهید'), // localized
checked: !Morebits.userIsSysop
},
{
label: 'برچسبزدن صفحه با الگوی حفاظت', // localized
value: 'tag',
tooltip: 'اگر مدیر حفاظتکننده فراموش کرده باشد برچسب بزند یا خودتان بدون افزودن برچسب حفاظت را اجرا کردهاید، از اینجا میتوانید برچسب را اعمال کنید', // localized
disabled: mw.config.get('wgArticleId') === 0 || mw.config.get('wgPageContentModel') === 'Scribunto'
}
]
});
form.append({ type: 'field', label: 'پیشفرض', name: 'field_preset' });
form.append({ type: 'field', label: '1', name: 'field1' });
form.append({ type: 'field', label: '2', name: 'field2' });
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
// We must init the controls
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.actiontype[0].dispatchEvent(evt);
// get current protection level asynchronously
Twinkle.protect.fetchProtectionLevel();
};
// Contains the current protection level in an object
// Once filled, it will look something like:
// { edit: { level: "sysop", expiry: <some date>, cascade: true }, ... }
Twinkle.protect.currentProtectionLevels = {};
// returns a jQuery Deferred object, usage:
// Twinkle.protect.fetchProtectingAdmin(apiObject, pageName, protect/stable).done(function(admin_username) { ...code... });
Twinkle.protect.fetchProtectingAdmin = function twinkleprotectFetchProtectingAdmin(api, pageName, protType, logIds) {
logIds = logIds || [];
return api.get({
format: 'json',
action: 'query',
list: 'logevents',
letitle: pageName,
letype: protType
}).then(function(data) {
// don't check log entries that have already been checked (e.g. don't go into an infinite loop!)
var event = data.query ? $.grep(data.query.logevents, function(le) {
return $.inArray(le.logid, logIds);
})[0] : null;
if (!event) {
// fail gracefully
return null;
} else if (event.action === 'move_prot' || event.action === 'move_stable') {
return twinkleprotectFetchProtectingAdmin(api, protType === 'protect' ? event.params.oldtitle_title : event.params.oldtitle, protType, logIds.concat(event.logid));
}
return event.user;
});
};
// mw.loader.getState('ext.flaggedRevs.review') returns null if the
// FlaggedRevs extension is not registered. Previously, this was done with
// wgFlaggedRevsParams, but after 1.34-wmf4 it is no longer exported if empty
// (https://gerrit.wikimedia.org/r/c/mediawiki/extensions/FlaggedRevs/+/508427)
Twinkle.protect.fetchProtectionLevel = function twinkleprotectFetchProtectionLevel() {
var api = new mw.Api();
var protectDeferred = api.get({
format: 'json',
indexpageids: true,
action: 'query',
list: 'logevents',
letype: 'protect',
letitle: mw.config.get('wgPageName'),
prop: mw.loader.getState('ext.flaggedRevs.review') ? 'info|flagged' : 'info',
inprop: 'protection',
titles: mw.config.get('wgPageName')
});
var stableDeferred = api.get({
format: 'json',
action: 'query',
list: 'logevents',
letype: 'stable',
letitle: mw.config.get('wgPageName')
});
var earlyDecision = [protectDeferred];
if (mw.loader.getState('ext.flaggedRevs.review')) {
earlyDecision.push(stableDeferred);
}
$.when.apply($, earlyDecision).done(function(protectData, stableData) {
// $.when.apply is supposed to take an unknown number of promises
// via an array, which it does, but the type of data returned varies.
// If there are two or more deferreds, it returns an array (of objects),
// but if there's just one deferred, it retuns a simple object.
// This is annoying.
protectData = $(protectData).toArray();
var pageid = protectData[0].query.pageids[0];
var page = protectData[0].query.pages[pageid];
var current = {}, adminEditDeferred;
$.each(page.protection, function(index, protection) {
if (protection.type !== 'aft') {
current[protection.type] = {
level: protection.level,
expiry: protection.expiry,
cascade: protection.cascade === ''
};
// logs report last admin who made changes to either edit/move/create protection, regardless if they only modified one of them
if (!adminEditDeferred) {
adminEditDeferred = Twinkle.protect.fetchProtectingAdmin(api, mw.config.get('wgPageName'), 'protect');
}
}
});
if (page.flagged) {
current.stabilize = {
level: page.flagged.protection_level,
expiry: page.flagged.protection_expiry
};
adminEditDeferred = Twinkle.protect.fetchProtectingAdmin(api, mw.config.get('wgPageName'), 'stable');
}
// show the protection level and log info
Twinkle.protect.hasProtectLog = !!protectData[0].query.logevents.length;
Twinkle.protect.hasStableLog = mw.loader.getState('ext.flaggedRevs.review') ? !!stableData[0].query.logevents.length : false;
Twinkle.protect.currentProtectionLevels = current;
if (adminEditDeferred) {
adminEditDeferred.done(function(admin) {
if (admin) {
$.each(['edit', 'move', 'create', 'stabilize'], function(i, type) {
if (Twinkle.protect.currentProtectionLevels[type]) {
Twinkle.protect.currentProtectionLevels[type].admin = admin;
}
});
}
Twinkle.protect.callback.showLogAndCurrentProtectInfo();
});
} else {
Twinkle.protect.callback.showLogAndCurrentProtectInfo();
}
});
};
Twinkle.protect.callback.showLogAndCurrentProtectInfo = function twinkleprotectCallbackShowLogAndCurrentProtectInfo() {
var currentlyProtected = !$.isEmptyObject(Twinkle.protect.currentProtectionLevels);
if (Twinkle.protect.hasProtectLog || Twinkle.protect.hasStableLog) {
var $linkMarkup = $('<span>');
if (Twinkle.protect.hasProtectLog) {
$linkMarkup.append(
$('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: mw.config.get('wgPageName'), type: 'protect'}) + '">protection log</a>'),
Twinkle.protect.hasStableLog ? $('<span> • </span>') : null
);
}
if (Twinkle.protect.hasStableLog) {
$linkMarkup.append($('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: mw.config.get('wgPageName'), type: 'stable'}) + '">pending changes log</a>)'));
}
Morebits.status.init($('div[name="hasprotectlog"] span')[0]);
Morebits.status.warn(
currentlyProtected ? 'محافظتهای پیشین' : 'این صفحه در گذشته مورد حفاظت قرار گرفتهاست', // localized
$linkMarkup[0]
);
}
Morebits.status.init($('div[name="currentprot"] span')[0]);
var protectionNode = [], statusLevel = 'info';
if (currentlyProtected) {
$.each(Twinkle.protect.currentProtectionLevels, function(type, settings) {
var label = type === 'stabilize' ? 'تغییرات در حال انتظار' : Morebits.string.toUpperCaseFirstChar(type); // localized
protectionNode.push($('<b>' + label + ': ' + settings.level + '</b>')[0]);
if (settings.expiry === 'infinity') {
protectionNode.push(' (بیپایان) '); // localized
} else {
protectionNode.push(' (زمان سرآمدن ' + new Date(settings.expiry).toUTCString() + ') '); // localized
}
if (settings.cascade) {
protectionNode.push('(آبشاری) '); // localized
}
if (settings.admin) {
var adminLink = '<a target="_blank" href="' + mw.util.getUrl('User talk:' + settings.admin) + '">' + settings.admin + '</a>';
protectionNode.push($('<span>توسط ' + adminLink + ' </span>')[0]); // localized
}
protectionNode.push($('<span> \u2022 </span>')[0]);
});
protectionNode = protectionNode.slice(0, -1); // remove the trailing bullet
statusLevel = 'warn';
} else {
protectionNode.push($('<b>محافظت نشده</b>')[0]); // localized
}
Morebits.status[statusLevel]('سطح محافظت فعلی', protectionNode); // localized
};
Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAction(e) {
var field_preset;
var field1;
var field2;
var isTemplate = mw.config.get('wgNamespaceNumber') === 10 || mw.config.get('wgNamespaceNumber') === 828;
switch (e.target.values) {
case 'protect':
field_preset = new Morebits.quickForm.element({ type: 'field', label: 'Preset', name: 'field_preset' });
field_preset.append({
type: 'select',
name: 'category',
label: 'انتخاب یک پیشفرض:', // localized
event: Twinkle.protect.callback.changePreset,
list: mw.config.get('wgArticleId') ?
Twinkle.protect.protectionTypes.filter(function(v) {
return isTemplate || v.label !== 'Template protection';
}) :
Twinkle.protect.protectionTypesCreate
});
field2 = new Morebits.quickForm.element({ type: 'field', label: 'گزینههای حفاظت', name: 'field2' }); // localized
field2.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback
field2.append({ type: 'div', name: 'hasprotectlog', label: ' ' });
// for existing pages
if (mw.config.get('wgArticleId')) {
field2.append({
type: 'checkbox',
name: 'editmodify',
event: Twinkle.protect.formevents.editmodify,
list: [
{
label: 'تغییر حفاظت در برابر ویرایش', // localized
value: 'editmodify',
tooltip: 'درصورت خاموشکردن این جعبه، سطح حفاظت از ویرایش و زمان سرآمدن به همان شکلی که هستند باقی میمانند', // localized
checked: true
}
]
});
var editlevel = field2.append({
type: 'select',
name: 'editlevel',
label: 'محافظت در برابر ویرایش:', // localized
event: Twinkle.protect.formevents.editlevel
});
editlevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
editlevel.append({
type: 'option',
label: 'تأییدشدهٔ خودکار', // localized
value: 'autoconfirmed'
});
editlevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed'
});
if (isTemplate) {
editlevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
}
editlevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
field2.append({
type: 'select',
name: 'editexpiry',
label: 'سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
// default expiry selection is conditionally set in Twinkle.protect.callback.changePreset
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'دلخواه', value: 'custom' } // localized
]
});
field2.append({
type: 'checkbox',
name: 'movemodify',
event: Twinkle.protect.formevents.movemodify,
list: [
{
label: 'تغییر حفاظت از انتقال', // localized
value: 'movemodify',
tooltip: 'اگر این جعبه خاموش باشد، سطح حفاظت از انتقال و زمان سرآمدن به همان شکلی که هستند باقیمیمانند', // localized
checked: true
}
]
});
var movelevel = field2.append({
type: 'select',
name: 'movelevel',
label: 'حفاظت دربرابر انتقال:', // localized
event: Twinkle.protect.formevents.movelevel
});
movelevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
movelevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed'
});
if (isTemplate) {
movelevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
}
movelevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
field2.append({
type: 'select',
name: 'moveexpiry',
label: 'سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
// default expiry selection is conditionally set in Twinkle.protect.callback.changePreset
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'دلخواه', value: 'custom' } // localized
]
});
// Namespaces hardcoded until [[phab:T218479]]
if (mw.loader.getState('ext.flaggedRevs.review') && (mw.config.get('wgNamespaceNumber') === 0 || mw.config.get('wgNamespaceNumber') === 4)) {
field2.append({
type: 'checkbox',
name: 'pcmodify',
event: Twinkle.protect.formevents.pcmodify,
list: [
{
label: 'تغییر حفاظت نیازمند بازبینی', // localized
value: 'pcmodify',
tooltip: 'اگر این جعبه خاموش باشد، سطح حفاظت نیازمند بازبینی و زمان سرآمدن به همان شکلی که هستند باقی میمانند', // localized
checked: true
}
]
});
var pclevel = field2.append({
type: 'select',
name: 'pclevel',
label: 'تغییرات درحال انتظار:', // localized
event: Twinkle.protect.formevents.pclevel
});
pclevel.append({
type: 'option',
label: 'هیچ', // localized
value: 'none'
});
pclevel.append({
type: 'option',
label: 'تغییرات در حال انتظار', // localized
value: 'autoconfirmed',
selected: true
});
field2.append({
type: 'select',
name: 'pcexpiry',
label: 'سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', selected: true, value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'دلخواه', value: 'custom' } // localized
]
});
}
} else { // for non-existing pages
var createlevel = field2.append({
type: 'select',
name: 'createlevel',
label: 'حفاظت در برابر ایجاد:', // localized
event: Twinkle.protect.formevents.createlevel
});
createlevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
if (mw.config.get('wgNamespaceNumber') !== 0) {
createlevel.append({
type: 'option',
label: 'تأیید خودکار', // localized
value: 'autoconfirmed'
});
}
if (isTemplate) {
createlevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
}
createlevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed',
selected: true
});
createlevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop'
});
field2.append({
type: 'select',
name: 'createexpiry',
label: 'سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', selected: true, value: 'indefinite' }, // localized
{ label: 'دلخواه', value: 'custom' } // localized
]
});
}
field2.append({
type: 'textarea',
name: 'protectReason',
label: 'دلیل (برای سیاههٔ محافظت):' // localized
});
if (!mw.config.get('wgArticleId') || mw.config.get('wgPageContentModel') === 'Scribunto') { // tagging isn't relevant for non-existing or module pages
break;
}
/* falls through */
case 'tag':
field1 = new Morebits.quickForm.element({ type: 'field', label: 'گزینههای برچسب زدن', name: 'field1' });
field1.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback
field1.append({ type: 'div', name: 'hasprotectlog', label: ' ' });
field1.append({
type: 'select',
name: 'tagtype',
label: 'انتخاب الگوی محافظت:', // localized
list: Twinkle.protect.protectionTags,
event: Twinkle.protect.formevents.tagtype
});
field1.append({
type: 'checkbox',
list: [
{
name: 'small',
label: 'کوچک (small=yes)', // localized
tooltip: 'استفاده از ویژگی |small=yes الگو، به شکلی که فقط قفل نمایش داده شود', // localized
checked: true
},
{
name: 'noinclude',
label: 'قراردادن الگوی حفاظت درون برچسب <noinclude>', // localized
tooltip: 'الگوی حفاظت را درون <noinclude> قرار میدهد تا تراگنجانش نشود', // localized
checked: mw.config.get('wgNamespaceNumber') === 10
}
]
});
break;
case 'request':
field_preset = new Morebits.quickForm.element({ type: 'field', label: 'نوع محافظت', name: 'field_preset' }); // localized
field_preset.append({
type: 'select',
name: 'category',
label: 'نوع و دلیل:', // localized
event: Twinkle.protect.callback.changePreset,
list: mw.config.get('wgArticleId') ? Twinkle.protect.protectionTypes : Twinkle.protect.protectionTypesCreate
});
field1 = new Morebits.quickForm.element({ type: 'field', label: 'گزینهها', name: 'field1' }); // localized
field1.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback
field1.append({ type: 'div', name: 'hasprotectlog', label: ' ' });
field1.append({
type: 'select',
name: 'expiry',
label: 'مدت: ', // localized
list: [
{ label: 'موقت', value: 'temporary' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: '', selected: true, value: '' }
]
});
field1.append({
type: 'textarea',
name: 'reason',
label: 'دلیل: ' // localized
});
break;
default:
alert("مشکلی در حین اجرای حفاظت توینکلی پیش آمد"); // localized
break;
}
var oldfield;
if (field_preset) {
oldfield = $(e.target.form).find('fieldset[name="field_preset"]')[0];
oldfield.parentNode.replaceChild(field_preset.render(), oldfield);
} else {
$(e.target.form).find('fieldset[name="field_preset"]').css('display', 'none');
}
if (field1) {
oldfield = $(e.target.form).find('fieldset[name="field1"]')[0];
oldfield.parentNode.replaceChild(field1.render(), oldfield);
} else {
$(e.target.form).find('fieldset[name="field1"]').css('display', 'none');
}
if (field2) {
oldfield = $(e.target.form).find('fieldset[name="field2"]')[0];
oldfield.parentNode.replaceChild(field2.render(), oldfield);
} else {
$(e.target.form).find('fieldset[name="field2"]').css('display', 'none');
}
if (e.target.values === 'protect') {
// fake a change event on the preset dropdown
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
e.target.form.category.dispatchEvent(evt);
// reduce vertical height of dialog
$(e.target.form).find('fieldset[name="field2"] select').parent().css({ display: 'inline-block', marginRight: '0.5em' });
}
// re-add protection level and log info, if it's available
Twinkle.protect.callback.showLogAndCurrentProtectInfo();
};
Twinkle.protect.formevents = {
editmodify: function twinkleprotectFormEditmodifyEvent(e) {
e.target.form.editlevel.disabled = !e.target.checked;
e.target.form.editexpiry.disabled = !e.target.checked || (e.target.form.editlevel.value === 'all');
e.target.form.editlevel.style.color = e.target.form.editexpiry.style.color = e.target.checked ? '' : 'transparent';
},
editlevel: function twinkleprotectFormEditlevelEvent(e) {
e.target.form.editexpiry.disabled = e.target.value === 'all';
},
movemodify: function twinkleprotectFormMovemodifyEvent(e) {
// sync move settings with edit settings if applicable
if (e.target.form.movelevel.disabled && !e.target.form.editlevel.disabled) {
e.target.form.movelevel.value = e.target.form.editlevel.value;
e.target.form.moveexpiry.value = e.target.form.editexpiry.value;
} else if (e.target.form.editlevel.disabled) {
e.target.form.movelevel.value = 'sysop';
e.target.form.moveexpiry.value = 'indefinite';
}
e.target.form.movelevel.disabled = !e.target.checked;
e.target.form.moveexpiry.disabled = !e.target.checked || (e.target.form.movelevel.value === 'all');
e.target.form.movelevel.style.color = e.target.form.moveexpiry.style.color = e.target.checked ? '' : 'transparent';
},
movelevel: function twinkleprotectFormMovelevelEvent(e) {
e.target.form.moveexpiry.disabled = e.target.value === 'all';
},
pcmodify: function twinkleprotectFormPcmodifyEvent(e) {
e.target.form.pclevel.disabled = !e.target.checked;
e.target.form.pcexpiry.disabled = !e.target.checked || (e.target.form.pclevel.value === 'none');
e.target.form.pclevel.style.color = e.target.form.pcexpiry.style.color = e.target.checked ? '' : 'transparent';
},
pclevel: function twinkleprotectFormPclevelEvent(e) {
e.target.form.pcexpiry.disabled = e.target.value === 'none';
},
createlevel: function twinkleprotectFormCreatelevelEvent(e) {
e.target.form.createexpiry.disabled = e.target.value === 'all';
},
tagtype: function twinkleprotectFormTagtypeEvent(e) {
e.target.form.small.disabled = e.target.form.noinclude.disabled = (e.target.value === 'none') || (e.target.value === 'noop');
}
};
Twinkle.protect.doCustomExpiry = function twinkleprotectDoCustomExpiry(target) {
var custom = prompt('یک زمان سرآمدن سفارشی وارد کنید. \n میتوانید زمانهای نسبی مثل «۱ دقیقه» یا «۱۹ روز» یا زمانمهر مطلق به شکل yyyymmddhhmm وارد کنید (به عنوان مثال "200602011405" نشان دهندهٔ اول فوریهٔ ۲۰۰۶، ساعت ۱۴:۰۵ جهانی است).', ''); // localized fixes might be necessary
if (custom) {
var option = document.createElement('option');
option.setAttribute('value', custom);
option.textContent = custom;
target.appendChild(option);
target.value = custom;
} else {
target.selectedIndex = 0;
}
};
Twinkle.protect.protectionTypes = [
{ label: 'خروج از حفاظت', value: 'unprotect' }, // localized
{
label: 'حفاظت کامل', // localized
list: [
{ label: 'کلی (کامل)', value: 'pp-protected' }, // localized
{ label: 'اختلاف نظر / جنگ ویرایشی (کامل)', value: 'pp-dispute' }, // localized
{ label: 'خرابکاری مداوم (کامل)', value: 'pp-vandalism' }, // localized
{ label: 'بحث کاربری بستهشده (کامل)', value: 'pp-usertalk' } // localized
]
},
{
label: 'حفاظت الگو', // localized
list: [
{ label: 'الگوی پرکاربرد', value: 'pp-template' } // localized
]
},
{
label: 'Extended confirmed protection',
list: [
{ label: 'تصمیم هیئت داوری', selected: true, value: 'pp-30-500-arb' }, // localized
{ label: 'خرابکاری پیدرپی', value: 'pp-30-500-vandalism' }, // localized
{ label: 'ویرایش فلجکننده', value: 'pp-30-500-disruptive' }, // localized
{ label: 'سرپیچی از سیاست زندگینامهٔ زندگان', value: 'pp-30-500-blp' }, // localized
{ label: 'زاپاسبازی', value: 'pp-30-500-sock' } // localized
]
},
{
label: 'Semi-protection',
list: [
{ label: 'کلی (نیمهحفاظت)', value: 'pp-semi-protected' }, // localized
{ label: 'خرابکاری ادامهدار (نیمهحفاظت)', selected: true, value: 'pp-semi-vandalism' }, // localized
{ label: 'ویرایش فلجکننده (نیمهحفاظت)', value: 'pp-semi-disruptive' }, // localized
{ label: 'افزودن مطالب بدون منبع (نیمه حفاظت)', value: 'pp-semi-unsourced' }, // localized
{ label: 'سرپیچی از سیاست زندگینامهٔ زندگان (نیمهحفاظت)', value: 'pp-semi-blp' }, // localized
{ label: 'زاپاسبازی (نیمهحفاظت)', value: 'pp-semi-sock' }, // localized
{ label: 'صفحهٔ بحث کاربر بستهشده (نیمهحفاظت)', value: 'pp-semi-usertalk' } // localized
]
},
{
label: 'تغییرات در حال انتظار', // localized
list: [
{ label: 'کلی', value: 'pp-pc-protected' }, // localized
{ label: 'خرابکاری پیدرپی', value: 'pp-pc-vandalism' }, // localized
{ label: 'ویرایش فلجکننده', value: 'pp-pc-disruptive' }, // localized
{ label: 'افزودن مطالب بیمنبع', value: 'pp-pc-unsourced' }, // localized
{ label: 'سرپیچی از سیاست زندگینامهٔ زندگان', value: 'pp-pc-blp' } // localized
]
},
{
label: 'حفاظت از انتقال', // localized
list: [
{ label: 'کلی (انتقال)', value: 'pp-move' }, // localized
{ label: 'اختلاف نظر/جنگ برسر عنوان (انتقال)', value: 'pp-move-dispute' }, // localized
{ label: 'خرابکاری از طریق انتقال (انتقال)', value: 'pp-move-vandalism' }, // localized
{ label: 'صفحهٔ پربازدید (انتقال)', value: 'pp-move-indef' } // localized
]
}
];
Twinkle.protect.protectionTypesCreate = [
{ label: 'خروج از محافظت', value: 'unprotect' }, // localized
{
label: 'محافظت در برابر ایجاد', // localized
list: [
{ label: 'کلی ({{pp-create}})', value: 'pp-create' }, // localized
{ label: 'نام نامناسب', value: 'pp-create-offensive' }, // localized
{ label: 'ایجادهای مجدد', selected: true, value: 'pp-create-salt' }, // localized
{ label: 'زندگینامهٔ زندگان که یه تازگی حذفشده', value: 'pp-create-blp' } // localized
]
}
];
// A page with both regular and PC protection will be assigned its regular
// protection weight plus 2
Twinkle.protect.protectionWeight = {
sysop: 40,
templateeditor: 30,
extendedconfirmed: 20,
autoconfirmed: 10,
flaggedrevs_autoconfirmed: 5, // Pending Changes protection alone
all: 0,
flaggedrevs_none: 0 // just in case
};
// NOTICE: keep this synched with [[MediaWiki:Protect-dropdown]]
// Also note: stabilize = Pending Changes level
Twinkle.protect.protectionPresetsInfo = {
'pp-protected': {
edit: 'sysop',
move: 'sysop',
reason: null
},
'pp-dispute': {
edit: 'sysop',
move: 'sysop',
reason: '[[وو:مناقشه|مناقشه بر سر محتوا/جنگ ویرایشی]]' // localized
},
'pp-vandalism': {
edit: 'sysop',
move: 'sysop',
reason: '[[وو:خ|خرابکاری]] ادامهدار' // localized
},
'pp-usertalk': {
edit: 'sysop',
move: 'sysop',
reason: '[[وو:حبک|استفادهٔ نادرست از صفحهٔ بحث کاربر در حین بندایش]]' // localized
},
'pp-template': {
edit: 'templateeditor',
move: 'templateeditor',
reason: '[[وو:حساس|الگوهای بسیار پرکاربرد]]' // localized
},
'pp-30-500-arb': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: '[[وو:قفل آبی|درخواست هیئت نظارت]]',
template: 'pp-30-500'
},
'pp-30-500-vandalism': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: ' [[وو:خ|خرابکاری]] ادامهدار از سوی حسابهای تأییدشده(یخودکار)', // localized
template: 'pp-30-500'
},
'pp-30-500-disruptive': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: '[[وو:اخلال|ویرایش اخلالگرانهٔ]] ادامهدار توسط کاربران تأیید شده', // localized
template: 'pp-30-500'
},
'pp-30-500-blp': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: 'نقض ادامهدار [[وو:زز|سیاست زندگینامه زندگان]] توسط کاربران تأیید شده', // localized
template: 'pp-30-500'
},
'pp-30-500-sock': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: 'Persistent [[وو:زاپاس|زاپاس بازی]]', // localized
template: 'pp-30-500'
},
'pp-semi-vandalism': {
edit: 'autoconfirmed',
reason: '[[وو:خرابکاری|خرابکاری]] ادامهدار', // localized
template: 'pp-vandalism'
},
'pp-semi-disruptive': {
edit: 'autoconfirmed',
reason: '[[وو:اخلال|ویرایش اخلالگرانهٔ]] ادامهدار', // localized
template: 'pp-protected'
},
'pp-semi-unsourced': {
edit: 'autoconfirmed',
reason: 'افزودن مداوم [[WP:INTREF|مطالب بدون منبع یا با منبع نامعتبر]]', // localized
template: 'pp-protected'
},
'pp-semi-blp': {
edit: 'autoconfirmed',
reason: 'Violations of the [[WP:BLP|biographies of living persons policy]]',
template: 'pp-blp'
},
'pp-semi-usertalk': {
edit: 'autoconfirmed',
move: 'autoconfirmed',
reason: '[[WP:PP#Talk-page protection|Inappropriate use of user talk page while blocked]]',
template: 'pp-usertalk'
},
'pp-semi-template': { // removed for now
edit: 'autoconfirmed',
move: 'autoconfirmed',
reason: '[[WP:High-risk templates|Highly visible template]]',
template: 'pp-template'
},
'pp-semi-sock': {
edit: 'autoconfirmed',
reason: 'Persistent [[WP:Sock puppetry|sock puppetry]]',
template: 'pp-sock'
},
'pp-semi-protected': {
edit: 'autoconfirmed',
reason: null,
template: 'pp-protected'
},
'pp-pc-vandalism': {
stabilize: 'autoconfirmed', // stabilize = Pending Changes
reason: '[[وو:خرابکاری|خرابکاری]] مداوم', // localized
template: 'pp-pc'
},
'pp-pc-disruptive': {
stabilize: 'autoconfirmed',
reason: '[[وو:اخلال|ویرایشهای اخلالگرانه]] ادامهدار', // localized
template: 'pp-pc'
},
'pp-pc-unsourced': {
stabilize: 'autoconfirmed',
reason: 'ادامهدار بودن [[WP:INTREF|افزودن مداوم مطالب بدون منبع یا منبع ضعیف]]', // localized
template: 'pp-pc'
},
'pp-pc-blp': {
stabilize: 'autoconfirmed',
reason: 'نقض [[وو:زنده|سیاست زندگینامه زندگان]]', // localized
template: 'pp-pc'
},
'pp-pc-protected': {
stabilize: 'autoconfirmed',
reason: null,
template: 'pp-pc'
},
'pp-move': {
move: 'sysop',
reason: null
},
'pp-move-dispute': {
move: 'sysop',
reason: '[[وو:قفل سبز|مناقشه بر سر نام]]' // localized
},
'pp-move-vandalism': {
move: 'sysop',
reason: '[[وو:قفل سبز|خرابکاری از طریق انتقال]]' // localized
},
'pp-move-indef': {
move: 'sysop',
reason: '[[وو:قفل سبز|صفحهٔ پربیننده]]' // localized
},
'unprotect': {
edit: 'all',
move: 'all',
stabilize: 'none',
create: 'all',
reason: null,
template: 'none'
},
'pp-create-offensive': {
create: 'sysop',
reason: '[[وو:نمک|نام نامناسب]]' // localized
},
'pp-create-salt': {
create: 'extendedconfirmed',
reason: '[[وو:نمک|ایجاد پیاپی]]' // localized
},
'pp-create-blp': {
create: 'extendedconfirmed',
reason: '[[WP:BLPDEL|Recently deleted BLP]]'
},
'pp-create': {
create: 'extendedconfirmed',
reason: '{{pp-create}}' // localized version to be decided, non-existing in source
}
};
Twinkle.protect.protectionTags = [
{
label: 'هیچ (حذف الگوهای حفاظت فعلی)', // localized
value: 'none'
},
{
label: 'هیچ (بدون حذف الگوهای حفاظت فعلی)', // localized
value: 'noop'
},
{
label: 'الگوهای حفاظت دربرابر ویرایش', // localized
list: [
{ label: '{{pp-vandalism}}: خرابکاری', value: 'pp-vandalism' }, // localized
{ label: '{{pp-dispute}}: مناقشه/جنگ ویرایشی', value: 'pp-dispute' }, // localized
{ label: '{{pp-blp}}: سرپیچی از سیاست زندگینامهٔ زندگان', value: 'pp-blp' }, // localized
{ label: '{{pp-sock}}: زاپاسبازی', value: 'pp-sock' }, // localized
{ label: '{{pp-template}}: الگوی حساس', value: 'pp-template' }, // localized
{ label: '{{pp-usertalk}}: صفحهٔ بحث کاربر بستهشده', value: 'pp-usertalk' }, // localized
{ label: '{{pp-protected}}: حفاظت کلی', value: 'pp-protected' }, // localized
{ label: '{{pp-semi-indef}}: نیمهحفاظت کلی و طولانیمدت', value: 'pp-semi-indef' }, // localized
{ label: '{{pp-30-500}}: نیمهحفاظت ویژه', value: 'pp-30-500' } // localized
]
},
{
label: 'الگوهای تغییرات درحال انتظار', // localized
list: [
{ label: '{{pp-pc}}: تغییرات در حال انتظار', value: 'pp-pc' } // localized
]
},
{
label: 'الگوهای حفاظت در برابر انتقال', // localized
list: [
{ label: '{{pp-move-dispute}}: مناقشه/جنگ بر سر انتقال', value: 'pp-move-dispute' }, // localized
{ label: '{{pp-move-vandalism}}: خرابکاری از طریق انتقال', value: 'pp-move-vandalism' }, // localized
{ label: '{{pp-move-indef}}: کلی و طولانیمدت', value: 'pp-move-indef' }, // localized
{ label: '{{pp-move}}: سایر', value: 'pp-move' } // localized
]
}
];
Twinkle.protect.callback.changePreset = function twinkleprotectCallbackChangePreset(e) {
var form = e.target.form;
var actiontypes = form.actiontype;
var actiontype;
for (var i = 0; i < actiontypes.length; i++) {
if (!actiontypes[i].checked) {
continue;
}
actiontype = actiontypes[i].values;
break;
}
if (actiontype === 'protect') { // actually protecting the page
var item = Twinkle.protect.protectionPresetsInfo[form.category.value];
if (mw.config.get('wgArticleId')) {
if (item.edit) {
form.editmodify.checked = true;
Twinkle.protect.formevents.editmodify({ target: form.editmodify });
form.editlevel.value = item.edit;
Twinkle.protect.formevents.editlevel({ target: form.editlevel });
form.editexpiry.value = '2 days';
} else {
form.editmodify.checked = false;
Twinkle.protect.formevents.editmodify({ target: form.editmodify });
}
if (item.move) {
form.movemodify.checked = true;
Twinkle.protect.formevents.movemodify({ target: form.movemodify });
form.movelevel.value = item.move;
Twinkle.protect.formevents.movelevel({ target: form.movelevel });
form.moveexpiry.value = '2 days';
} else {
form.movemodify.checked = false;
Twinkle.protect.formevents.movemodify({ target: form.movemodify });
}
if (form.pcmodify) {
if (item.stabilize) {
form.pcmodify.checked = true;
Twinkle.protect.formevents.pcmodify({ target: form.pcmodify });
form.pclevel.value = item.stabilize;
Twinkle.protect.formevents.pclevel({ target: form.pclevel });
} else {
form.pcmodify.checked = false;
Twinkle.protect.formevents.pcmodify({ target: form.pcmodify });
}
}
} else {
if (item.create) {
form.createlevel.value = item.create;
Twinkle.protect.formevents.createlevel({ target: form.createlevel });
}
}
var reasonField = actiontype === 'protect' ? form.protectReason : form.reason;
if (item.reason) {
reasonField.value = item.reason;
} else {
reasonField.value = '';
}
// sort out tagging options, disabled if nonexistent or lua
if (mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') !== 'Scribunto') {
if (form.category.value === 'unprotect') {
form.tagtype.value = 'none';
} else {
form.tagtype.value = item.template ? item.template : form.category.value;
}
Twinkle.protect.formevents.tagtype({ target: form.tagtype });
if (/template/.test(form.category.value)) {
form.noinclude.checked = true;
form.editexpiry.value = form.moveexpiry.value = form.pcexpiry.value = 'indefinite';
} else if (mw.config.get('wgNamespaceNumber') !== 10) {
form.noinclude.checked = false;
}
}
} else { // RPP request
if (form.category.value === 'unprotect') {
form.expiry.value = '';
form.expiry.disabled = true;
} else {
form.expiry.value = '';
form.expiry.disabled = false;
}
}
};
Twinkle.protect.callback.evaluate = function twinkleprotectCallbackEvaluate(e) {
var form = e.target;
var actiontypes = form.actiontype;
var actiontype;
for (var i = 0; i < actiontypes.length; i++) {
if (!actiontypes[i].checked) {
continue;
}
actiontype = actiontypes[i].values;
break;
}
var tagparams;
if (actiontype === 'tag' || (actiontype === 'protect' && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') !== 'Scribunto')) {
tagparams = {
tag: form.tagtype.value,
reason: (form.tagtype.value === 'pp-protected' || form.tagtype.value === 'pp-semi-protected' || form.tagtype.value === 'pp-move') && form.protectReason ? form.protectReason.value : null,
small: form.small.checked,
noinclude: form.noinclude.checked
};
}
switch (actiontype) {
case 'protect':
// protect the page
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'محافظت کامل شد'; // localized
var statusInited = false;
var thispage;
var allDone = function twinkleprotectCallbackAllDone() {
if (thispage) {
thispage.getStatusElement().info('done');
}
if (tagparams) {
Twinkle.protect.callbacks.taggingPageInitial(tagparams);
}
};
var stabilizeValues = {};
if (form.pclevel) {
stabilizeValues = {
pclevel: form.pclevel.value,
pcexpiry: form.pcexpiry.value,
protectReason: form.protectReason.value
};
}
var protectIt = function twinkleprotectCallbackProtectIt(next) {
thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), ' محافظت صفحه'); // localized
if (mw.config.get('wgArticleId')) {
if (form.editmodify.checked) {
thispage.setEditProtection(form.editlevel.value, form.editexpiry.value);
}
if (form.movemodify.checked) {
thispage.setMoveProtection(form.movelevel.value, form.moveexpiry.value);
}
} else {
thispage.setCreateProtection(form.createlevel.value, form.createexpiry.value);
thispage.setWatchlist(false);
}
if (form.protectReason.value) {
thispage.setEditSummary(form.protectReason.value);
} else {
alert('میبایست دلیلی برای حفاظت جهت ذکر در سیاههٔ محافظت ارائه دهید.'); // localized
return;
}
if (!statusInited) {
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
statusInited = true;
}
thispage.protect(next);
};
var stabilizeIt = function twinkleprotectCallbackStabilizeIt() {
if (thispage) {
thispage.getStatusElement().info('done');
}
thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Applying pending changes protection');
thispage.setFlaggedRevs(stabilizeValues.pclevel, stabilizeValues.pcexpiry);
if (stabilizeValues.protectReason) {
thispage.setEditSummary(stabilizeValues.protectReason);
} else {
alert('باید دلیلی برای درخواست ذکر کنید تا در سیاهه محافظت ثبت شود.'); // localized
return;
}
if (!statusInited) {
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
statusInited = true;
}
thispage.stabilize(allDone, function(error) {
if (error.errorCode === 'stabilize_denied') { // [[phab:T234743]]
thispage.getStatusElement().error('Failed trying to modify pending changes settings, likely due to a mediawiki bug. Other actions (tagging or regular protection) may have taken place. Please reload the page and try again.');
}
});
};
if ((form.editmodify && form.editmodify.checked) || (form.movemodify && form.movemodify.checked) ||
!mw.config.get('wgArticleId')) {
if (form.pcmodify && form.pcmodify.checked) {
protectIt(stabilizeIt);
} else {
protectIt(allDone);
}
} else if (form.pcmodify && form.pcmodify.checked) {
stabilizeIt();
} else {
alert("لطفا دستوری به توینکل دهید.\nاگر صرفا قصد برچسبزدن دارید، گزینهٔ 'برچسب زدن صفحه' را از بالاانتخاب کنید."); // localized
}
break;
case 'tag':
// apply a protection template
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.followRedirect = false;
Morebits.wiki.actionCompleted.notice = 'برچسب زدن کامل شد'; // localized
Twinkle.protect.callbacks.taggingPageInitial(tagparams);
break;
case 'request':
// file request at RFPP
var typename, typereason;
switch (form.category.value) {
case 'pp-dispute':
case 'pp-vandalism':
case 'pp-usertalk':
case 'pp-protected':
typename = 'full protection';
break;
case 'pp-template':
typename = 'template protection';
break;
case 'pp-30-500-arb':
case 'pp-30-500-vandalism':
case 'pp-30-500-disruptive':
case 'pp-30-500-blp':
case 'pp-30-500-sock':
typename = 'extended confirmed protection';
break;
case 'pp-semi-vandalism':
case 'pp-semi-disruptive':
case 'pp-semi-unsourced':
case 'pp-semi-usertalk':
case 'pp-semi-sock':
case 'pp-semi-blp':
case 'pp-semi-protected':
typename = 'semi-protection';
break;
case 'pp-pc-vandalism':
case 'pp-pc-blp':
case 'pp-pc-protected':
case 'pp-pc-unsourced':
case 'pp-pc-disruptive':
typename = 'pending changes';
break;
case 'pp-move':
case 'pp-move-dispute':
case 'pp-move-indef':
case 'pp-move-vandalism':
typename = 'move protection';
break;
case 'pp-create':
case 'pp-create-offensive':
case 'pp-create-blp':
case 'pp-create-salt':
typename = 'create protection';
break;
case 'unprotect':
var admins = $.map(Twinkle.protect.currentProtectionLevels, function(pl) {
return pl.admin ? 'User:' + pl.admin : null;
});
if (admins.length && !confirm('آیا قبلا با مدیران حفاظت کننده (' + $.unique(admins).join(', ') + ') تماس گرفتهاید؟')) { // localized
return false;
}
// otherwise falls through
default:
typename = 'unprotection';
break;
}
switch (form.category.value) {
case 'pp-dispute':
typereason = 'مناقشه بر سر محتوا/جنگ ویرایشی'; // localized
break;
case 'pp-vandalism':
case 'pp-semi-vandalism':
case 'pp-pc-vandalism':
case 'pp-30-500-vandalism':
typereason = ' [[وو:خ|خرابکاری]] ادامهدار'; // localized
break;
case 'pp-semi-disruptive':
case 'pp-pc-disruptive':
case 'pp-30-500-disruptive':
typereason = ' [[Wikipedia:Disruptive editing|ویرایش فلجکنندهٔ ]] ادامهدار'; // localized
break;
case 'pp-semi-unsourced':
case 'pp-pc-unsourced':
typereason = '[[WP:INTREF|مطالب بدون منبع]] افزودن ادامهدار'; // localized
break;
case 'pp-template':
typereason = '[[WP:HIGHRISK|الگوی حساس]]'; // localized
break;
case 'pp-30-500-arb':
typereason = '[[WP:30/500|تصمیم هیئت داوری]]'; // localized
break;
case 'pp-usertalk':
case 'pp-semi-usertalk':
typereason = 'استفادهٔ نادرست از صفحهٔ بحث کاربر در حین بندایش'; // localized
break;
case 'pp-semi-sock':
case 'pp-30-500-sock':
typereason = '[[وو:ز|زاپاسبازی]] ادامهدار'; // localized
break;
case 'pp-semi-blp':
case 'pp-pc-blp':
case 'pp-30-500-blp':
typereason = 'سرپیچی از سیاست زندگینامهٔ زندگان'; // localized
break;
case 'pp-move-dispute':
typereason = 'مناقشه/جنگ بر سر عنوان/انتقال'; // localized
break;
case 'pp-move-vandalism':
typereason = 'خرابکاری از طریق اناقال صفحات'; // localized
break;
case 'pp-move-indef':
typereason = 'صفحهٔ پربازدید'; // localized
break;
case 'pp-create-offensive':
typereason = 'نام نامناسب'; // localized
break;
case 'pp-create-blp':
typereason = 'زندگینامهٔ زندگان که به تازگی حذف شده'; // localized
break;
case 'pp-create-salt':
typereason = 'ایجادهای پیدرپی'; // localized
break;
default:
typereason = '';
break;
}
var reason = typereason;
if (form.reason.value !== '') {
if (typereason !== '') {
reason += '\u00A0\u2013 '; // U+00A0 NO-BREAK SPACE; U+2013 EN RULE
}
reason += form.reason.value;
}
if (reason !== '' && reason.charAt(reason.length - 1) !== '.') {
reason += '.';
}
var rppparams = {
reason: reason,
typename: typename,
category: form.category.value,
expiry: form.expiry.value
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
var rppName = 'ویکیواژه:درخواست محافظت از صفحه'; // localized
// Updating data for the action completed event
Morebits.wiki.actionCompleted.redirect = rppName;
Morebits.wiki.actionCompleted.notice = 'درخواست کامل شد، درحال بازگشت به صفحه بحث'; // localized
var rppPage = new Morebits.wiki.page(rppName, 'درحال درخواست برای محافظت از صفحه'); // localized
rppPage.setFollowRedirect(true);
rppPage.setCallbackParameters(rppparams);
rppPage.load(Twinkle.protect.callbacks.fileRequest);
break;
default:
alert('twinkleprotect: unknown kind of action');
break;
}
};
Twinkle.protect.callbacks = {
taggingPageInitial: function(tagparams) {
if (tagparams.tag === 'noop') {
Morebits.status.info(' اعمال الگوی حفاظت', 'کاری برای انجام نیست'); // localized
return;
}
var protectedPage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'برچسب زدن صفحه'); // localized
protectedPage.setCallbackParameters(tagparams);
protectedPage.load(Twinkle.protect.callbacks.taggingPage);
},
taggingPage: function(protectedPage) {
var params = protectedPage.getCallbackParameters();
var text = protectedPage.getPageText();
var tag, summary;
var oldtag_re = /\s*(?:<noinclude>)?\s*\{\{\s*(pp-[^{}]*?|protected|(?:t|v|s|p-|usertalk-v|usertalk-s|sb|move)protected(?:2)?|protected template|privacy protection)\s*?\}\}\s*(?:<\/noinclude>)?\s*/gi;
var re_result = oldtag_re.exec(text);
if (re_result) {
if (params.tag === 'none' || confirm('{{' + re_result[1] + '}} در صفحه یافت نشد. \nبرای حذف آن تأیید و برای نگهداشتن آن لغو را کلیک کنید.')) { // localized
text = text.replace(oldtag_re, '');
}
}
if (params.tag === 'none') {
summary = 'حذف الگوی حفاظت' + Twinkle.getPref('summaryAd'); // localized
} else {
tag = params.tag;
if (params.reason) {
tag += '|reason=' + params.reason;
}
if (params.small) {
tag += '|small=yes';
}
if (/^\s*#redirect/i.test(text)) { // redirect page
// Only tag if no {{rcat shell}} is found
if (!text.match(/{{(?:redr|this is a redirect|r(?:edirect)?(?:.?cat.*)?[ _]?sh)/i)) {
text = text.replace(/#REDIRECT ?(\[\[.*?\]\])(.*)/i, '#REDIRECT $1$2\n\n{{' + tag + '}}');
} else {
Morebits.status.info('پوستهٔ ردهٔ تغییر مسیر موجود است', 'کاری برای انجام وجود ندارد'); // localized
return;
}
} else if (params.noinclude) {
text = '<noinclude>{{' + tag + '}}</noinclude>' + text;
} else {
text = '{{' + tag + '}}\n' + text;
}
summary = 'افزودن {{' + params.tag + '}}' + Twinkle.getPref('summaryAd'); // localized
}
protectedPage.setEditSummary(summary);
protectedPage.setPageText(text);
protectedPage.setCreateOption('nocreate');
protectedPage.suppressProtectWarning(); // no need to let admins know they are editing through protection
protectedPage.save();
},
fileRequest: function(rppPage) {
var params = rppPage.getCallbackParameters();
var text = rppPage.getPageText();
var statusElement = rppPage.getStatusElement();
var rppRe = new RegExp('===\\s*(\\[\\[)?\\s*:?\\s*' + RegExp.escape(Morebits.pageNameNorm, true) + '\\s*(\\]\\])?\\s*===', 'm');
var tag = rppRe.exec(text);
var rppLink = document.createElement('a');
rppLink.setAttribute('href', mw.util.getUrl(rppPage.getPageName()));
rppLink.appendChild(document.createTextNode(rppPage.getPageName()));
if (tag) {
statusElement.error([ 'در حال حاضر درخواست حفاظتی برای این صفحه در ', rppLink, ' موجود است، ادامه نمیدهیم' ]); // localized
return;
}
var newtag = '=== [[:' + Morebits.pageNameNorm + ']] ===\n';
if (new RegExp('^' + RegExp.escape(newtag).replace(/\s+/g, '\\s*'), 'm').test(text)) {
statusElement.error([ 'در حال حاضر درخواست حفاظتی برای این صفحه در ', rppLink, ' موجود است، ادامه نمیدهیم' ]); // localized
return;
}
newtag += '* {{pagelinks|1=' + Morebits.pageNameNorm + '}}\n\n';
var words;
switch (params.expiry) {
case 'temporary':
words = 'موقت '; // localized
break;
case 'indefinite':
words = 'بیپایان '; // localized
break;
default:
words = '';
break;
}
words += params.typename;
newtag += "'''" + Morebits.string.toUpperCaseFirstChar(words) + (params.reason !== '' ? ":''' " +
Morebits.string.formatReasonText(params.reason) : ".'''") + ' ~~~~';
// If either protection type results in a increased status, then post it under increase
// else we post it under decrease
var increase = false;
var protInfo = Twinkle.protect.protectionPresetsInfo[params.category];
// function to compute protection weights (see comment at Twinkle.protect.protectionWeight)
var computeWeight = function(mainLevel, stabilizeLevel) {
var result = Twinkle.protect.protectionWeight[mainLevel || 'all'];
if (stabilizeLevel) {
if (result) {
if (stabilizeLevel.level === 'autoconfirmed') {
result += 2;
}
} else {
result = Twinkle.protect.protectionWeight['flaggedrevs_' + stabilizeLevel];
}
}
return result;
};
// compare the page's current protection weights with the protection we are requesting
var editWeight = computeWeight(Twinkle.protect.currentProtectionLevels.edit &&
Twinkle.protect.currentProtectionLevels.edit.level,
Twinkle.protect.currentProtectionLevels.stabilize &&
Twinkle.protect.currentProtectionLevels.stabilize.level);
if (computeWeight(protInfo.edit, protInfo.stabilize) > editWeight ||
computeWeight(protInfo.move) > computeWeight(Twinkle.protect.currentProtectionLevels.move &&
Twinkle.protect.currentProtectionLevels.move.level) ||
computeWeight(protInfo.create) > computeWeight(Twinkle.protect.currentProtectionLevels.create &&
Twinkle.protect.currentProtectionLevels.create.level)) {
increase = true;
}
var reg;
if (increase) {
reg = /(\n==\s*Current requests for reduction in protection level\s*==)/;
} else {
reg = /(\n==\s*Current requests for edits to a protected page\s*==)/;
}
var originalTextLength = text.length;
text = text.replace(reg, '\n' + newtag + '\n$1');
if (text.length === originalTextLength) {
var linknode = document.createElement('a');
linknode.setAttribute('href', mw.util.getUrl('Wikipedia:Twinkle/Fixing RPP'));
linknode.appendChild(document.createTextNode('How to fix RPP'));
statusElement.error([ 'عنوانی مناسب در وو:دمص یافت نشد. برای حل این مشکل ', linknode, ' را ببینید' ]); // localized
return;
}
statusElement.status('افزودن درخواست جدید...'); // localized
rppPage.setEditSummary('/* ' + Morebits.pageNameNorm + ' */ Requesting ' + params.typename + (params.typename === 'pending changes' ? ' on [[:' : ' of [[:') +
Morebits.pageNameNorm + ']].' + Twinkle.getPref('summaryAd'));
rppPage.setPageText(text);
rppPage.setCreateOption('recreate');
rppPage.save();
}
};
})(jQuery);
// </nowiki>
/* ===== friendlytag.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** friendlytag.js: Tag module
****************************************
* Mode of invocation: Tab ("Tag")
* Active on: Existing articles and drafts; file pages with a corresponding file
* which is local (not on Commons); all redirects
*/
Twinkle.tag = function friendlytag() {
// redirect tagging
if (Morebits.wiki.isPageRedirect()) {
Twinkle.tag.mode = 'redirect';
Twinkle.addPortletLink(Twinkle.tag.callback, 'برچسب', 'friendly-tag', 'تغییرمسیر برچسب'); // localized
// file tagging
} else if (mw.config.get('wgNamespaceNumber') === 6 && !document.getElementById('mw-sharedupload') && document.getElementById('mw-imagepage-section-filehistory')) {
Twinkle.tag.mode = 'file';
Twinkle.addPortletLink(Twinkle.tag.callback, 'برچسب', 'friendly-tag', 'زدن برچسبهای نگهداری به پرونده'); // localized
// article/draft article tagging
} else if ([0, 118].indexOf(mw.config.get('wgNamespaceNumber')) !== -1 && mw.config.get('wgCurRevisionId')) {
Twinkle.tag.mode = 'article';
// Can't remove tags when not viewing current version
Twinkle.tag.canRemove = (mw.config.get('wgCurRevisionId') === mw.config.get('wgRevisionId')) &&
// Disabled on latest diff because the diff slider could be used to slide
// away from the latest diff without causing the script to reload
!mw.config.get('wgDiffNewId');
Twinkle.addPortletLink(Twinkle.tag.callback, 'برچسب', 'friendly-tag', 'افزودن یا حذف برچسبهای نگهداری مقاله'); // localized
}
};
Twinkle.tag.checkedTags = [];
Twinkle.tag.callback = function friendlytagCallback() {
var Window = new Morebits.simpleWindow(630, Twinkle.tag.mode === 'article' ? 500 : 400);
Window.setScriptName('Twinkle');
// anyone got a good policy/guideline/info page/instructional page link??
Window.addFooterLink('Twinkle help', 'WP:TW/DOC#tag');
var form = new Morebits.quickForm(Twinkle.tag.callback.evaluate);
form.append({
type: 'input',
label: 'پالایش سریع: ', // localized
name: 'quickfilter',
size: '30px',
event: function twinkletagquickfilter() {
// flush the DOM of all existing underline spans
$allCheckboxDivs.find('.search-hit').each(function(i, e) {
var label_element = e.parentElement;
// This would convert <label>Hello <span class=search-hit>wo</span>rld</label>
// to <label>Hello world</label>
label_element.innerHTML = label_element.textContent;
});
if (this.value) {
$allCheckboxDivs.hide();
$allHeaders.hide();
var searchString = this.value;
var searchRegex = new RegExp(mw.util.escapeRegExp(searchString), 'i');
$allCheckboxDivs.find('label').each(function () {
var label_text = this.textContent;
var searchHit = searchRegex.exec(label_text);
if (searchHit) {
var range = document.createRange();
var textnode = this.childNodes[0];
range.selectNodeContents(textnode);
range.setStart(textnode, searchHit.index);
range.setEnd(textnode, searchHit.index + searchString.length);
var underline_span = $('<span>').addClass('search-hit').css('text-decoration', 'underline')[0];
range.surroundContents(underline_span);
this.parentElement.style.display = 'block'; // show
}
});
} else {
$allCheckboxDivs.show();
$allHeaders.show();
}
}
});
switch (Twinkle.tag.mode) {
case 'article':
Window.setTitle('Article maintenance tagging');
form.append({
type: 'select',
name: 'sortorder',
label: 'مشاهده این فهرست:', // localized
tooltip: 'میتوانید ترتیب پیشفرض نمایش را در ترجیحات توینکل خود تغییر دهید (وو:توینکل تر).', // localized
event: Twinkle.tag.updateSortOrder,
list: [
{ type: 'option', value: 'cat', label: 'بر پایه رده', selected: Twinkle.getPref('tagArticleSortOrder') === 'cat' }, // localized
{ type: 'option', value: 'alpha', label: 'بهترتیب الفبا', selected: Twinkle.getPref('tagArticleSortOrder') === 'alpha' } // localized
]
});
if (!Twinkle.tag.canRemove) {
var divElement = document.createElement('div');
divElement.innerHTML = 'برای حذف برچسبهای موجود، لطفاً منوی برچسب را از نسخهٔ کنونی مقاله باز کنید'; // localized
form.append({
type: 'div',
name: 'untagnotice',
label: divElement
});
}
form.append({
type: 'div',
id: 'tagWorkArea',
className: 'morebits-scrollbox',
style: 'max-height: 28em'
});
form.append({
type: 'checkbox',
list: [
{
label: 'گروهبندی برچسبها در {{مشکلات متعدد}}، در صورت امکان', // localized
value: 'group',
name: 'group',
tooltip: 'در صورتی که در حال افزودن دو یا چند الگویی که توسط {{مشکلات متعدد}} پشتیبانی میشوند هستید و این گزینه نیز انتخاب شدهاست، تمامی الگوهای پشتیبانیشده در درون الگوی {{مشکلات متعدد}} گروهبندی خواهند شد.', // localized
checked: Twinkle.getPref('groupByDefault')
}
]
});
form.append({
type: 'input',
label: 'دلیل',
name: 'reason',
tooltip: 'دلیل اختیاری برای پیوستشدن به خلاصه ویرایش. ذکر دلیل بههنگام حذف برچسبها توصیه میشود.', // localized
size: '60px'
});
break;
case 'file':
Window.setTitle('File maintenance tagging');
form.append({ type: 'header', label: 'برچسبهای مشکلات مجوز و منبعدهی' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.licenseList });
form.append({ type: 'header', label: 'برچسبهای مرتبط با ویکیانبار' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.commonsList });
form.append({ type: 'header', label: 'برچسبهای تمیزکاری' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.cleanupList });
form.append({ type: 'header', label: 'برچسبهای کیفیت تصویر' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.qualityList });
form.append({ type: 'header', label: 'برچسبهای جایگزینی' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.replacementList });
if (Twinkle.getPref('customFileTagList').length) {
form.append({ type: 'header', label: 'برچسبهای سفارشی' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.getPref('customFileTagList') });
}
break;
case 'redirect':
Window.setTitle('Redirect tagging');
form.append({ type: 'header', label: 'Spelling, misspelling, tense and capitalization templates' });
form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.spellingList });
form.append({ type: 'header', label: 'الگوهای نام جایگزین' }); // localized
form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.alternativeList });
form.append({ type: 'header', label: 'الگوهای تغییرمسیر گوناگون و مدیریتی' }); // localized
form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.administrativeList });
if (Twinkle.getPref('customRedirectTagList').length) {
form.append({ type: 'header', label: 'برچسبهای سفارشی' }); // localized
form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.getPref('customRedirectTagList') });
}
break;
default:
alert('Twinkle.tag: unknown mode ' + Twinkle.tag.mode);
break;
}
if (document.getElementsByClassName('patrollink').length) {
form.append({
type: 'checkbox',
list: [
{
label: 'علامت زدن صفحه بهعنوان گشتخورده/بازبینیشده', // localized
value: 'patrolPage',
name: 'patrolPage',
checked: Twinkle.getPref('markTaggedPagesAsPatrolled')
}
]
});
}
form.append({ type: 'submit', className: 'tw-tag-submit' });
var result = form.render();
Window.setContent(result);
Window.display();
// for quick filter:
$allCheckboxDivs = $(result).find('[name$=Tags]').parent();
$allHeaders = $(result).find('h5');
result.quickfilter.focus(); // place cursor in the quick filter field as soon as window is opened
result.quickfilter.autocomplete = 'off'; // disable browser suggestions
result.quickfilter.addEventListener('keypress', function(e) {
if (e.keyCode === 13) { // prevent enter key from accidentally submitting the form
e.preventDefault();
return false;
}
});
if (Twinkle.tag.mode === 'article') {
Twinkle.tag.alreadyPresentTags = [];
if (Twinkle.tag.canRemove) {
// Look for existing maintenance tags in the lead section and put them in array
// All tags are HTML table elements that are direct children of .mw-parser-output,
// except when they are within {{multiple issues}}
$('.mw-parser-output').children().each(function parsehtml(i, e) {
// break out on encountering the first heading, which means we are no
// longer in the lead section
if (e.tagName === 'H2') {
return false;
}
// The ability to remove tags depends on the template's {{ambox}} |name=
// parameter bearing the template's correct name (preferably) or a name that at
// least redirects to the actual name
// All tags have their first class name as "box-" + template name
if (e.className.indexOf('box-') === 0) {
if (e.classList[0] === 'box-Multiple_issues') {
$(e).find('.ambox').each(function(idx, e) {
var tag = e.classList[0].slice(4).replace(/_/g, ' ');
Twinkle.tag.alreadyPresentTags.push(tag);
});
return true; // continue
}
var tag = e.classList[0].slice(4).replace(/_/g, ' ');
Twinkle.tag.alreadyPresentTags.push(tag);
}
});
// {{Uncategorized}} and {{Improve categories}} are usually placed at the end
if ($('.box-Uncategorized').length) {
Twinkle.tag.alreadyPresentTags.push('Uncategorized');
}
if ($('.box-Improve_categories').length) {
Twinkle.tag.alreadyPresentTags.push('Improve categories');
}
}
// Add status text node after Submit button
var statusNode = document.createElement('small');
statusNode.id = 'tw-tag-status';
Twinkle.tag.status = {
// initial state; defined like this because these need to be available for reference
// in the click event handler
numAdded: 0,
numRemoved: 0
};
$('button.tw-tag-submit').after(statusNode);
// fake a change event on the sort dropdown, to initialize the tag list
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.sortorder.dispatchEvent(evt);
} else {
// Redirects and files: Add a link to each template's description page
Morebits.quickForm.getElements(result, Twinkle.tag.mode + 'Tags').forEach(generateLinks);
}
};
// $allCheckboxDivs and $allHeaders are defined globally, rather than in the
// quickfilter event function, to avoid having to recompute them on every keydown
var $allCheckboxDivs, $allHeaders;
Twinkle.tag.updateSortOrder = function(e) {
var form = e.target.form;
var sortorder = e.target.value;
Twinkle.tag.checkedTags = form.getChecked('articleTags') || [];
var container = new Morebits.quickForm.element({ type: 'fragment' });
// function to generate a checkbox, with appropriate subgroup if needed
var makeCheckbox = function(tag, description) {
var checkbox = { value: tag, label: '{{' + tag + '}}: ' + description };
if (Twinkle.tag.checkedTags.indexOf(tag) !== -1) {
checkbox.checked = true;
}
switch (tag) {
case 'Cleanup':
checkbox.subgroup = {
name: 'cleanup',
type: 'input',
label: 'دلیل مشخص برای اینکه چرا به تمیزکاری نیاز است: ', // localized
tooltip: 'الزامی.', // localized
size: 35
};
break;
case 'Close paraphrasing':
checkbox.subgroup = {
name: 'closeParaphrasing',
type: 'input',
label: 'منبع: ', // localized
tooltip: 'منبعی که بهطور نزدیک بازنویسی شدهاست' // localized
};
break;
case 'Copy edit':
checkbox.subgroup = {
name: 'copyEdit',
type: 'input',
label: '"این مقاله ممکن است نیازمند ویرایستاری باشد..." ', // localized
tooltip: 'بهعنوان مثال، "املای یکپارچه". انتخابی.', // localized
size: 35
};
break;
case 'Copypaste':
checkbox.subgroup = {
name: 'copypaste',
type: 'input',
label: 'نشانی منبع: ', // localized
tooltip: 'اگر مشخص است.', // localized
size: 50
};
break;
case 'Expand language':
checkbox.subgroup = [ {
name: 'expandLanguageLangCode',
type: 'input',
label: 'شناسه زبان: ', // localized
tooltip: 'شناسهٔ زبانی که مقاله باید از روی آن گسترش دادهشود' // localized
}, {
name: 'expandLanguageArticle',
type: 'input',
label: 'عنوان مقاله: ', // localized
tooltip: 'عنوان مقالهای که گسترش از روی آن انجام میشود، بدون پیشوند میانویکی' // localized
}
];
break;
case 'Expert needed':
checkbox.subgroup = [
{
name: 'expertNeeded',
type: 'input',
label: 'عنوان ویکیپروژهٔ مرتبط: ', // localized
tooltip: 'بهطور انتخابی، نام ویکیپروژهای که میتواند به جذب یک کاربر باتجربه کمک کند را وارد کنید. بدون پیشوند «ویکیپروژه».' // localized
},
{
name: 'expertNeededReason',
type: 'input',
label: 'دلیل: ', // localized
tooltip: 'توضیح کوتاه برای توصیف مشکل. پیوند به دلیل یا بحث الزامی است.' // localized
},
{
name: 'expertNeededTalk',
type: 'input',
label: 'ریسهٔ بحث: ', // localized
tooltip: 'عنوان بخشی از صفحهٔ بحث این مقاله که بحث دربارهٔ مشکل در آن جریان دارد. نیاز به درج پیوند نیست، تنها عنوان بخش را وارد کنید. پیوند به دلیل یا بحث الزامی است.' // localized
}
];
break;
case 'Globalize':
checkbox.subgroup = {
name: 'globalizeRegion',
type: 'input',
label: 'منطقه یا کشورِ بیش از اندازه معرفیشده' // localized
};
break;
case 'History merge':
checkbox.subgroup = [
{
name: 'histmergeOriginalPage',
type: 'input',
label: 'مقالهٔ دیگر: ', // localized
tooltip: 'عنوان صفحهای که میتواند در این مقاله ادغام شود (الزامی).' // localized
},
{
name: 'histmergeReason',
type: 'input',
label: 'دلیل: ', // localized
tooltip: 'توضیح کوتاه که دلیل نیاز به ادغام تاریخچه را بیان کند. بهتر است با عبارت «زیرا» آغاز شده و با یک نقطه پایان یابد.' // localized
},
{
name: 'histmergeSysopDetails',
type: 'input',
label: 'جزئیات بیشتر: ', // localized
tooltip: 'برای موارد پیچیده، راهنماییهای بیشتری را در اختیار مدیر بررسیکننده قرار دهید.' // localized
}
];
break;
case 'Merge':
case 'Merge from':
case 'Merge to':
var otherTagName = 'Merge';
switch (tag) {
case 'Merge from':
otherTagName = 'Merge to';
break;
case 'Merge to':
otherTagName = 'Merge from';
break;
// no default
}
checkbox.subgroup = [
{
name: 'mergeTarget',
type: 'input',
label: 'مقاله(های) دیگر: ', // localized
tooltip: 'اگر قصد دارید مقالههای متعدد را مشخص کنید، آنها را با نویسهٔ لوله از هم جدا کنید: مقالهٔ اول|مقالهٔ دوم' // localized
},
{
name: 'mergeTagOther',
type: 'checkbox',
list: [
{
label: 'مقالهٔ دیگر را با یک برچسب {{' + otherTagName + '}} مشخص کنید', // localized
checked: true,
tooltip: 'فقط در صورتی که تنها عنوان یک مقاله وارد شدهباشد، در دسترس است.' // localized
}
]
}
];
if (mw.config.get('wgNamespaceNumber') === 0) {
checkbox.subgroup.push({
name: 'mergeReason',
type: 'textarea',
label: 'دلیل اصلی برای ادغام (بر روی صفحهٔ بحث' +
(tag === 'Merge to' ? 'the other article\'s' : 'this article\'s') + ' قرار خواهد گرفت):', // localized
tooltip: 'انتخابی، اما بهشدت توصیهشده. در صورت عدم تمایل، خالی بگذارید. فقط در صورتی که تنها عنوان یک مقاله وارد شدهباشد، در دسترس است.' // localized
});
}
break;
case 'Not English':
case 'Rough translation':
checkbox.subgroup = [
{
name: 'translationLanguage',
type: 'input',
label: 'زبان مقاله (اگر مشخص است): ', // localized
tooltip: 'برای راهنمایی، مرور [[en:WP:LRC]] را در نظر داشتهباشید. اگر مقاله را در صفحهٔ مقالههای نیازمند ترجمه فهرست میکنید، لطفاً از خالیگذاشتن این جعبه خودداری کنید؛ مگر آنکه کاملاً نامطمؤن باشید.' // localized
}
];
if (tag === 'Not English') {
checkbox.subgroup.push({
name: 'translationNotify',
type: 'checkbox',
list: [
{
label: 'اطلاعرسانی به سازندهٔ مقاله', // localized
checked: true,
tooltip: "الگوی {{uw-notenglish}} را در صفحهٔ بحث سازندهٔ مقاله قرار میدهد." // localized
}
]
});
}
if (mw.config.get('wgNamespaceNumber') === 0) {
checkbox.subgroup.push({
name: 'translationPostAtPNT',
type: 'checkbox',
list: [
{
label: 'فهرستکردن این مقاله در ویکیپدیا:صفحههای نیازمند ترجمه به فارسی (PNT)', // localized
checked: true
}
]
});
checkbox.subgroup.push({
name: 'translationComments',
type: 'textarea',
label: 'توضیح بیشتر برای ارسال به ویکیپدیا:صفحههای نیازمند ترجمه به فارسی', // localized
tooltip: 'انتخابی، و متناسب تنها در صورتی که "فهرستکردن این مقاله..." در بالا انتخاب شدهباشد.' // localized
});
}
break;
case 'Notability':
checkbox.subgroup = {
name: 'notability',
type: 'select',
list: [
{ label: "{{سرشناسی}}: موضوع مقاله ممکن است رهنمود اصلی سرشناسی را احراز نکند", value: 'none' }, // localized
{ label: '{{سرشناسی|دانشگاهیان}}: رهنمود سرشناسی دانشگاهیان', value: 'Academics' }, // localized
{ label: '{{سرشناسی|اجرام آسمانی}}: رهنمود سرشناسی اجرام آسمانی', value: 'Astro' }, // localized
{ label: '{{سرشناسی|افراد}}: رهنمود سرشناسی زندگینامه افراد', value: 'Biographies' }, // localized
{ label: '{{سرشناسی|کتابها}}: رهنمود سرشناسی کتاب', value: 'Books' }, // localized
{ label: '{{سرشناسی|سازمانها}}: رهنمودهای سرشناسی شرکتها و سازمانها', value: 'Companies' }, // localized
{ label: '{{سرشناسی|رویدادها}}: رهنمود سرشناسی برای رویدادها', value: 'Events' }, // localized
{ label: '{{سرشناسی|فیلمها}}: رهنمود سرشناسی فیلمها', value: 'Films' }, // localized
{ label: '{{سرشناسی|مکان}}: رهنمود سرشناسی ویژگیهای جغرافیایی', value: 'Geographic' }, // localized
{ label: '{{سرشناسی|فهرستها}}: رهنمود سرشناسی فهرستهای مسقل', value: 'Lists' }, // localized
{ label: '{{سرشناسی|موسیقی}}: رهنمود سرشناسی موسیقی', value: 'Music' }, // localized
{ label: '{{سرشناسی|نوواژهها}}: رهنمود سرشناسی نوواژهها', value: 'Neologisms' }, // localized
{ label: '{{سرشناسی|عددها}}: رهنمود سرشناسی عددها', value: 'Numbers' }, // localized
{ label: '{{سرشناسی|محصولات}}: رهنمود سرشناسی محصولات و خدمات', value: 'Products' }, // localized
{ label: '{{سرشناسی|ورزشها}}: رهنمود سرشناسی ورزشها', value: 'Sports' }, // localized
{ label: '{{سرشناسی|تلویزیون}}: رهنمود سرشناسی برنامههای تلویزیونی', value: 'Television' }, // localized
{ label: '{{سرشناسی|وب}}: رهنمود سرشناسی مجتوای وب', value: 'Web' } // localized
]
};
break;
default:
break;
}
return checkbox;
};
var makeCheckboxesForAlreadyPresentTags = function() {
container.append({ type: 'header', id: 'tagHeader0', label: 'Tags already present' });
var subdiv = container.append({ type: 'div', id: 'tagSubdiv0' });
var checkboxes = [];
var unCheckedTags = e.target.form.getUnchecked('alreadyPresentArticleTags') || [];
Twinkle.tag.alreadyPresentTags.forEach(function(tag) {
var description = Twinkle.tag.article.tags[tag];
var checkbox =
{
value: tag,
label: '{{' + tag + '}}' + (description ? ': ' + description : ''),
checked: unCheckedTags.indexOf(tag) === -1,
style: 'font-style: italic'
};
checkboxes.push(checkbox);
});
subdiv.append({
type: 'checkbox',
name: 'alreadyPresentArticleTags',
list: checkboxes
});
};
if (sortorder === 'cat') { // categorical sort order
// function to iterate through the tags and create a checkbox for each one
var doCategoryCheckboxes = function(subdiv, array) {
var checkboxes = [];
$.each(array, function(k, tag) {
var description = Twinkle.tag.article.tags[tag];
if (Twinkle.tag.alreadyPresentTags.indexOf(tag) === -1) {
checkboxes.push(makeCheckbox(tag, description));
}
});
subdiv.append({
type: 'checkbox',
name: 'articleTags',
list: checkboxes
});
};
if (Twinkle.tag.alreadyPresentTags.length > 0) {
makeCheckboxesForAlreadyPresentTags();
}
var i = 1;
// go through each category and sub-category and append lists of checkboxes
$.each(Twinkle.tag.article.tagCategories, function(title, content) {
container.append({ type: 'header', id: 'tagHeader' + i, label: title }); // localized
var subdiv = container.append({ type: 'div', id: 'tagSubdiv' + i++ });
if (Array.isArray(content)) {
doCategoryCheckboxes(subdiv, content);
} else {
$.each(content, function(subtitle, subcontent) {
subdiv.append({ type: 'div', label: [ Morebits.htmlNode('b', subtitle) ] });
doCategoryCheckboxes(subdiv, subcontent);
});
}
});
} else { // alphabetical sort order
if (Twinkle.tag.alreadyPresentTags.length > 0) {
makeCheckboxesForAlreadyPresentTags();
container.append({ type: 'header', id: 'tagHeader1', label: 'برچسبهای قابل استفاده' }); // localized
}
var checkboxes = [];
$.each(Twinkle.tag.article.tags, function(tag, description) {
if (Twinkle.tag.alreadyPresentTags.indexOf(tag) === -1) {
checkboxes.push(makeCheckbox(tag, description));
}
});
container.append({
type: 'checkbox',
name: 'articleTags',
list: checkboxes
});
}
// append any custom tags
if (Twinkle.getPref('customTagList').length) {
container.append({ type: 'header', label: 'برچسبهای سفارشی' }); // localized
container.append({ type: 'checkbox', name: 'articleTags',
list: Twinkle.getPref('customTagList').map(function(el) {
el.checked = Twinkle.tag.checkedTags.indexOf(el.value) !== -1;
return el;
})
});
}
var $workarea = $(form).find('#tagWorkArea');
var rendered = container.render();
$workarea.empty().append(rendered);
// for quick filter:
$allCheckboxDivs = $workarea.find('[name$=Tags]').parent();
$allHeaders = $workarea.find('h5, .quickformDescription');
form.quickfilter.value = ''; // clear search, because the search results are not preserved over mode change
form.quickfilter.focus();
// style adjustments
$workarea.find('h5').css({ 'font-size': '110%' });
$workarea.find('h5:not(:first-child)').css({ 'margin-top': '1em' });
$workarea.find('div').filter(':has(span.quickformDescription)').css({ 'margin-top': '0.4em' });
var alreadyPresentTags = Morebits.quickForm.getElements(form, 'alreadyPresentArticleTags');
if (alreadyPresentTags) {
alreadyPresentTags.forEach(generateLinks);
}
// in the unlikely case that *every* tag is already on the page
var notPresentTags = Morebits.quickForm.getElements(form, 'articleTags');
if (notPresentTags) {
notPresentTags.forEach(generateLinks);
}
// tally tags added/removed, update statusNode text
var statusNode = document.getElementById('tw-tag-status');
$('[name=articleTags], [name=alreadyPresentArticleTags]').click(function() {
if (this.name === 'articleTags') {
Twinkle.tag.status.numAdded += this.checked ? 1 : -1;
} else if (this.name === 'alreadyPresentArticleTags') {
Twinkle.tag.status.numRemoved += this.checked ? -1 : 1;
}
var firstPart = 'Adding ' + Twinkle.tag.status.numAdded + ' tag' + (Twinkle.tag.status.numAdded > 1 ? 's' : '');
var secondPart = 'Removing ' + Twinkle.tag.status.numRemoved + ' tag' + (Twinkle.tag.status.numRemoved > 1 ? 's' : '');
statusNode.textContent =
(Twinkle.tag.status.numAdded ? ' ' + firstPart : '') +
(Twinkle.tag.status.numRemoved ? (Twinkle.tag.status.numAdded ? '; ' : ' ') + secondPart : '');
});
};
/**
* Adds a link to each template's description page
* @param {Morebits.quickForm.element} checkbox associated with the template
*/
var generateLinks = function(checkbox) {
var link = Morebits.htmlNode('a', '>');
link.setAttribute('class', 'tag-template-link');
var tagname = checkbox.values;
link.setAttribute('href', mw.util.getUrl(
(tagname.indexOf(':') === -1 ? 'Template:' : '') +
(tagname.indexOf('|') === -1 ? tagname : tagname.slice(0, tagname.indexOf('|')))
));
link.setAttribute('target', '_blank');
$(checkbox).parent().append(['\u00A0', link]);
};
// Tags for ARTICLES start here
Twinkle.tag.article = {};
// A list of all article tags, in alphabetical order
// To ensure tags appear in the default "categorized" view, add them to the tagCategories hash below.
Twinkle.tag.article.tags = {
'ویکیسازی': 'نیازمند ویکیسازی',
'منبع': 'نیازمند منبع',
'ابهامزدایی': 'ابهامزدایی'
};
// A list of tags in order of category
// Tags should be in alphabetical order within the categories
// Add new categories with discretion - the list is long enough as is!
Twinkle.tag.article.tagCategories = {
'برچسبهای نگهداری': [
'ویکیسازی',
'منبع',
'ابهامزدایی'
]
};
// Contains those article tags that *do not* work inside {{multiple issues}}.
Twinkle.tag.multipleIssuesExceptions = [
'Copypaste',
'Current', // Works but not intended for use in MI
'Expand language',
'GOCEinuse',
'History merge',
'Improve categories',
'In use',
'Merge',
'Merge from',
'Merge to',
'Not English',
'Rough translation',
'Uncategorized',
'Under construction'
];
// Tags for REDIRECTS start here
Twinkle.tag.spellingList = [];
Twinkle.tag.alternativeList = [];
Twinkle.tag.administrativeList = [];
// maintenance tags for FILES start here
Twinkle.tag.file = {};
Twinkle.tag.file.licenseList = [];
Twinkle.tag.file.commonsList = [];
Twinkle.tag.file.cleanupList = [];
Twinkle.tag.file.qualityList = [];
Twinkle.tag.file.replacementList = [];
Twinkle.tag.callbacks = {
article: function articleCallback(pageobj) {
// Remove tags that become superfluous with this action
var pageText = pageobj.getPageText().replace(/\{\{\s*([Uu]serspace draft)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/g, '');
var params = pageobj.getCallbackParameters();
/**
* Saves the page following the removal of tags if any. The last step.
* Called from removeTags()
*/
var postRemoval = function() {
if (params.tagsToRemove.length) {
// Remove empty {{multiple issues}} if found
pageText = pageText.replace(/\{\{(multiple ?issues|article ?issues|mi)\s*\|\s*\}\}\n?/im, '');
// Remove single-element {{multiple issues}} if found
pageText = pageText.replace(/\{\{(?:multiple ?issues|article ?issues|mi)\s*\|\s*(\{\{[^}]+\}\})\s*\}\}/im, '$1');
}
// Build edit summary
var makeSentence = function(array) {
if (array.length < 3) {
return array.join(' and ');
}
var last = array.pop();
return array.join(', ') + ', and ' + last;
};
var makeTemplateLink = function(tag) {
var text = '{{[[';
// if it is a custom tag with a parameter
if (tag.indexOf('|') !== -1) {
tag = tag.slice(0, tag.indexOf('|'));
}
text += tag.indexOf(':') !== -1 ? tag : 'Template:' + tag + '|' + tag;
return text + ']]}}';
};
var summaryText;
var addedTags = params.tags.map(makeTemplateLink);
var removedTags = params.tagsToRemove.map(makeTemplateLink);
if (addedTags.length) {
summaryText = 'Added ' + makeSentence(addedTags);
summaryText += removedTags.length ? '; and removed ' + makeSentence(removedTags) : '';
} else {
summaryText = 'Removed ' + makeSentence(removedTags);
}
summaryText += ' tag' + (addedTags.length + removedTags.length > 1 ? 's' : '');
if (params.reason) {
summaryText += ': ' + params.reason;
}
// avoid truncated summaries
if (summaryText.length > (499 - Twinkle.getPref('summaryAd').length)) {
summaryText = summaryText.replace(/\[\[[^|]+\|([^\]]+)\]\]/g, '$1');
}
pageobj.setPageText(pageText);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages'));
pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor'));
pageobj.setCreateOption('nocreate');
pageobj.save(function() {
// special functions for merge tags
if (params.mergeReason) {
// post the rationale on the talk page (only operates in main namespace)
var talkpageText = '\n\n== ' + params.talkDiscussionTitleLinked + ' ==\n\n';
talkpageText += params.mergeReason.trim() + ' ~~~~';
var talkpage = new Morebits.wiki.page('Talk:' + params.discussArticle, 'Posting rationale on talk page');
talkpage.setAppendText(talkpageText);
talkpage.setEditSummary('/* ' + params.talkDiscussionTitle + ' */ new section' + Twinkle.getPref('summaryAd'));
talkpage.setWatchlist(Twinkle.getPref('watchMergeDiscussions'));
talkpage.setCreateOption('recreate');
talkpage.append();
}
if (params.mergeTagOther) {
// tag the target page if requested
var otherTagName = 'Merge';
if (params.mergeTag === 'Merge from') {
otherTagName = 'Merge to';
} else if (params.mergeTag === 'Merge to') {
otherTagName = 'Merge from';
}
var newParams = {
tags: [otherTagName],
tagsToRemove: [],
tagsToRemain: [],
mergeTarget: Morebits.pageNameNorm,
discussArticle: params.discussArticle,
talkDiscussionTitle: params.talkDiscussionTitle,
talkDiscussionTitleLinked: params.talkDiscussionTitleLinked
};
var otherpage = new Morebits.wiki.page(params.mergeTarget, 'Tagging other page (' +
params.mergeTarget + ')');
otherpage.setCallbackParameters(newParams);
otherpage.load(Twinkle.tag.callbacks.article);
}
// post at WP:PNT for {{not English}} and {{rough translation}} tag
if (params.translationPostAtPNT) {
var pntPage = new Morebits.wiki.page('Wikipedia:Pages needing translation into English',
'Listing article at Wikipedia:Pages needing translation into English');
pntPage.setFollowRedirect(true);
pntPage.load(function friendlytagCallbacksTranslationListPage(pageobj) {
var old_text = pageobj.getPageText();
var template = params.tags.indexOf('Rough translation') !== -1 ? 'duflu' : 'needtrans';
var lang = params.translationLanguage;
var reason = params.translationComments;
var templateText = '{{subst:' + template + '|pg=' + Morebits.pageNameNorm + '|Language=' +
(lang || 'uncertain') + '|Comments=' + reason.trim() + '}} ~~~~';
var text, summary;
if (template === 'duflu') {
text = old_text + '\n\n' + templateText;
summary = 'Translation cleanup requested on ';
} else {
text = old_text.replace(/\n+(==\s?Translated pages that could still use some cleanup\s?==)/,
'\n\n' + templateText + '\n\n$1');
summary = 'Translation' + (lang ? ' from ' + lang : '') + ' requested on ';
}
if (text === old_text) {
pageobj.getStatusElement().error('failed to find target spot for the discussion');
return;
}
pageobj.setPageText(text);
pageobj.setEditSummary(summary + ' [[:' + Morebits.pageNameNorm + ']]' + Twinkle.getPref('summaryAd'));
pageobj.setCreateOption('recreate');
pageobj.save();
});
}
if (params.translationNotify) {
pageobj.lookupCreation(function(innerPageobj) {
var initialContrib = innerPageobj.getCreator();
// Disallow warning yourself
if (initialContrib === mw.config.get('wgUserName')) {
innerPageobj.getStatusElement().warn('You (' + initialContrib + ') created this page; skipping user notification');
return;
}
var userTalkPage = new Morebits.wiki.page('User talk:' + initialContrib,
'Notifying initial contributor (' + initialContrib + ')');
var notifytext = '\n\n== Your article [[' + Morebits.pageNameNorm + ']]==\n' +
'{{subst:uw-notenglish|1=' + Morebits.pageNameNorm +
(params.translationPostAtPNT ? '' : '|nopnt=yes') + '}} ~~~~';
userTalkPage.setAppendText(notifytext);
userTalkPage.setEditSummary('Notice: Please use English when contributing to the English Wikipedia.' +
Twinkle.getPref('summaryAd'));
userTalkPage.setCreateOption('recreate');
userTalkPage.setFollowRedirect(true);
userTalkPage.append();
});
}
});
if (params.patrol) {
pageobj.triage();
}
};
/**
* Removes the existing tags that were deselected (if any)
* Calls postRemoval() when done
*/
var removeTags = function removeTags() {
if (params.tagsToRemove.length === 0) {
postRemoval();
return;
}
Morebits.status.info('Info', 'Removing deselected tags that were already present');
var getRedirectsFor = [];
// Remove the tags from the page text, if found in its proper name,
// otherwise moves it to `getRedirectsFor` array earmarking it for
// later removal
params.tagsToRemove.forEach(function removeTag(tag) {
var tag_re = new RegExp('\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]+)?\\}\\}\\n?');
if (tag_re.test(pageText)) {
pageText = pageText.replace(tag_re, '');
} else {
getRedirectsFor.push('Template:' + tag);
}
});
if (!getRedirectsFor.length) {
postRemoval();
return;
}
// Remove tags which appear in page text as redirects
var api = new Morebits.wiki.api('Getting template redirects', {
'action': 'query',
'prop': 'linkshere',
'titles': getRedirectsFor.join('|'),
'redirects': 1, // follow redirect if the class name turns out to be a redirect page
'lhnamespace': '10', // template namespace only
'lhshow': 'redirect',
'lhlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
}, function removeRedirectTag(apiobj) {
$(apiobj.responseXML).find('page').each(function(idx, page) {
var removed = false;
$(page).find('lh').each(function(idx, el) {
var tag = $(el).attr('title').slice(9);
var tag_re = new RegExp('\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]*)?\\}\\}\\n?');
if (tag_re.test(pageText)) {
pageText = pageText.replace(tag_re, '');
removed = true;
return false; // break out of $.each
}
});
if (!removed) {
Morebits.status.warn('Info', 'Failed to find {{' +
$(page).attr('title').slice(9) + '}} on the page... excluding');
}
});
postRemoval();
});
api.post();
};
if (!params.tags.length) {
removeTags();
return;
}
var tagRe, tagText = '', tags = [], groupableTags = [], groupableExistingTags = [];
// Executes first: addition of selected tags
/**
* Updates `tagText` with the syntax of `tagName` template with its parameters
* @param {number} tagIndex
* @param {string} tagName
*/
var addTag = function articleAddTag(tagIndex, tagName) {
var currentTag = '';
if (tagName === 'Uncategorized' || tagName === 'Improve categories') {
pageText += '\n\n{{' + tagName + '|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}';
} else {
currentTag += '{{' + tagName;
// fill in other parameters, based on the tag
switch (tagName) {
case 'Cleanup':
currentTag += '|reason=' + params.cleanup;
break;
case 'Close paraphrasing':
currentTag += '|source=' + params.closeParaphrasing;
break;
case 'Copy edit':
if (params.copyEdit) {
currentTag += '|for=' + params.copyEdit;
}
break;
case 'Copypaste':
if (params.copypaste) {
currentTag += '|url=' + params.copypaste;
}
break;
case 'Expand language':
currentTag += '|topic=';
currentTag += '|langcode=' + params.expandLanguageLangCode;
if (params.expandLanguageArticle !== null) {
currentTag += '|otherarticle=' + params.expandLanguageArticle;
}
break;
case 'Expert needed':
if (params.expertNeeded) {
currentTag += '|1=' + params.expertNeeded;
}
if (params.expertNeededTalk) {
currentTag += '|talk=' + params.expertNeededTalk;
}
if (params.expertNeededReason) {
currentTag += '|reason=' + params.expertNeededReason;
}
break;
case 'Globalize':
currentTag += '|1=article';
if (params.globalizeRegion) {
currentTag += '|2=' + params.globalizeRegion;
}
break;
case 'News release':
currentTag += '|1=article';
break;
case 'Notability':
if (params.notability !== 'none') {
currentTag += '|' + params.notability;
}
break;
case 'Not English':
case 'Rough translation':
if (params.translationLanguage) {
currentTag += '|1=' + params.translationLanguage;
}
if (params.translationPostAtPNT) {
currentTag += '|listed=yes';
}
break;
case 'History merge':
currentTag += '|originalpage=' + params.histmergeOriginalPage;
if (params.histmergeReason) {
currentTag += '|reason=' + params.histmergeReason;
}
if (params.histmergeSysopDetails) {
currentTag += '|details=' + params.histmergeSysopDetails;
}
break;
case 'Merge':
case 'Merge to':
case 'Merge from':
params.mergeTag = tagName;
// normalize the merge target for now and later
params.mergeTarget = Morebits.string.toUpperCaseFirstChar(params.mergeTarget.replace(/_/g, ' '));
currentTag += '|' + params.mergeTarget;
// link to the correct section on the talk page, for article space only
if (mw.config.get('wgNamespaceNumber') === 0 && (params.mergeReason || params.discussArticle)) {
if (!params.discussArticle) {
// discussArticle is the article whose talk page will contain the discussion
params.discussArticle = tagName === 'Merge to' ? params.mergeTarget : mw.config.get('wgTitle');
// nonDiscussArticle is the article which won't have the discussion
params.nonDiscussArticle = tagName === 'Merge to' ? mw.config.get('wgTitle') : params.mergeTarget;
var direction = '[[' + params.nonDiscussArticle + ']]' + (params.mergeTag === 'Merge' ? ' with ' : ' into ') + '[[' + params.discussArticle + ']]';
params.talkDiscussionTitleLinked = 'Proposed merge of ' + direction;
params.talkDiscussionTitle = params.talkDiscussionTitleLinked.replace(/\[\[(.*?)\]\]/g, '$1');
}
currentTag += '|discuss=Talk:' + params.discussArticle + '#' + params.talkDiscussionTitle;
}
break;
default:
break;
}
currentTag += '|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}\n';
tagText += currentTag;
}
};
/**
* Adds the tags which go outside {{multiple issues}}, either because
* these tags aren't supported in {{multiple issues}} or because
* {{multiple issues}} is not being added to the page at all
*/
var addUngroupedTags = function() {
$.each(tags, addTag);
// Smartly insert the new tags after any hatnotes or
// afd, csd, or prod templates or hatnotes. Regex is
// extra complicated to allow for templates with
// parameters and to handle whitespace properly.
pageText = pageText.replace(
new RegExp(
// leading whitespace
'^\\s*' +
// capture template(s)
'(?:((?:\\s*' +
// AfD is special, as the tag includes html comments before and after the actual template
'(?:<!--.*AfD.*\\n\\{\\{(?:Article for deletion\\/dated|AfDM).*\\}\\}\\n<!--.*(?:\\n<!--.*)?AfD.*(?:\\s*\\n))?|' + // trailing whitespace/newline needed since this subst's a newline
// begin template format
'\\{\\{\\s*(?:' +
// CSD
'db|delete|db-.*?|speedy deletion-.*?|' +
// PROD
'(?:proposed deletion|prod blp)\\/dated(?:\\s*\\|(?:concern|user|timestamp|help).*)+|' +
// various hatnote templates
'about|correct title|dablink|distinguish|for|other\\s?(?:hurricaneuses|people|persons|places|uses(?:of)?)|redirect(?:-acronym)?|see\\s?(?:also|wiktionary)|selfref|short description|the' +
// not a hatnote, but sometimes under a CSD or AfD
'|salt|proposed deletion endorsed' +
// end main template name, optionally with a number (such as redirect2)
')\\d*\\s*' +
// template parameters
'(\\|(?:\\{\\{[^{}]*\\}\\}|[^{}])*)?' +
// end template format
'\\}\\})+' +
// end capture
'(?:\\s*\\n)?)' +
// trailing whitespace
'\\s*)?',
'i'), '$1' + tagText
);
removeTags();
};
// Separate tags into groupable ones (`groupableTags`) and non-groupable ones (`tags`)
params.tags.forEach(function(tag) {
tagRe = new RegExp('\\{\\{' + tag + '(\\||\\}\\})', 'im');
// regex check for preexistence of tag can be skipped if in canRemove mode
if (Twinkle.tag.canRemove || !tagRe.exec(pageText)) {
// condition Twinkle.tag.article.tags[tag] to ensure that its not a custom tag
// Custom tags are assumed non-groupable, since we don't know whether MI template supports them
if (Twinkle.tag.article.tags[tag] && Twinkle.tag.multipleIssuesExceptions.indexOf(tag) === -1) {
groupableTags.push(tag);
} else {
tags.push(tag);
}
} else {
if (tag === 'Merge from' || tag === 'History merge') {
tags.push(tag);
} else {
Morebits.status.warn('Info', 'Found {{' + tag +
'}} on the article already...excluding');
// don't do anything else with merge tags
if (['Merge', 'Merge to'].indexOf(tag) !== -1) {
params.mergeTarget = params.mergeReason = params.mergeTagOther = null;
}
}
}
});
// To-be-retained existing tags that are groupable
params.tagsToRemain.forEach(function(tag) {
if (Twinkle.tag.multipleIssuesExceptions.indexOf(tag) === -1) {
groupableExistingTags.push(tag);
}
});
var miTest = /\{\{(multiple ?issues|article ?issues|mi)(?!\s*\|\s*section\s*=)[^}]+\{/im.exec(pageText);
if (miTest && groupableTags.length > 0) {
Morebits.status.info('Info', 'Adding supported tags inside existing {{multiple issues}} tag');
tagText = '';
$.each(groupableTags, addTag);
var miRegex = new RegExp('(\\{\\{\\s*' + miTest[1] + '\\s*(?:\\|(?:\\{\\{[^{}]*\\}\\}|[^{}])*)?)\\}\\}\\s*', 'im');
pageText = pageText.replace(miRegex, '$1' + tagText + '}}\n');
tagText = '';
addUngroupedTags();
} else if (params.group && !miTest && (groupableExistingTags.length + groupableTags.length) >= 2) {
Morebits.status.info('Info', 'Grouping supported tags inside {{multiple issues}}');
tagText += '{{Multiple issues|\n';
/**
* Adds newly added tags to MI
*/
var addNewTagsToMI = function() {
$.each(groupableTags, addTag);
tagText += '}}\n';
addUngroupedTags();
};
var getRedirectsFor = [];
// Reposition the tags on the page into {{multiple issues}}, if found with its
// proper name, else moves it to `getRedirectsFor` array to be handled later
groupableExistingTags.forEach(function repositionTagIntoMI(tag) {
var tag_re = new RegExp('(\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]+)?\\}\\}\\n?)');
if (tag_re.test(pageText)) {
tagText += tag_re.exec(pageText)[1];
pageText = pageText.replace(tag_re, '');
} else {
getRedirectsFor.push('Template:' + tag);
}
});
if (!getRedirectsFor.length) {
addNewTagsToMI();
return;
}
var api = new Morebits.wiki.api('Getting template redirects', {
'action': 'query',
'prop': 'linkshere',
'titles': getRedirectsFor.join('|'),
'redirects': 1,
'lhnamespace': '10', // template namespace only
'lhshow': 'redirect',
'lhlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
}, function replaceRedirectTag(apiobj) {
$(apiobj.responseXML).find('page').each(function(idx, page) {
var found = false;
$(page).find('lh').each(function(idx, el) {
var tag = $(el).attr('title').slice(9);
var tag_re = new RegExp('(\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]*)?\\}\\}\\n?)');
if (tag_re.test(pageText)) {
tagText += tag_re.exec(pageText)[1];
pageText = pageText.replace(tag_re, '');
found = true;
return false; // break out of $.each
}
});
if (!found) {
Morebits.status.warn('Info', 'Failed to find the existing {{' +
$(page).attr('title').slice(9) + '}} on the page... skip repositioning');
}
});
addNewTagsToMI();
});
api.post();
} else {
tags = tags.concat(groupableTags);
addUngroupedTags();
}
},
redirect: function redirect(pageobj) {
var params = pageobj.getCallbackParameters(),
pageText = pageobj.getPageText(),
tagRe, tagText = '', summaryText = 'Added',
tags = [], i;
for (i = 0; i < params.tags.length; i++) {
tagRe = new RegExp('(\\{\\{' + params.tags[i] + '(\\||\\}\\}))', 'im');
if (!tagRe.exec(pageText)) {
tags.push(params.tags[i]);
} else {
Morebits.status.warn('Info', 'Found {{' + params.tags[i] +
'}} on the redirect already...excluding');
}
}
var addTag = function redirectAddTag(tagIndex, tagName) {
tagText += '\n{{' + tagName;
if (tagName === 'R from alternative language') {
if (params.altLangFrom) {
tagText += '|from=' + params.altLangFrom;
}
if (params.altLangTo) {
tagText += '|to=' + params.altLangTo;
}
}
tagText += '}}';
if (tagIndex > 0) {
if (tagIndex === (tags.length - 1)) {
summaryText += ' and';
} else if (tagIndex < (tags.length - 1)) {
summaryText += ',';
}
}
summaryText += ' {{[[:' + (tagName.indexOf(':') !== -1 ? tagName : 'Template:' + tagName + '|' + tagName) + ']]}}';
};
tags.sort();
$.each(tags, addTag);
// Check for all Rcat shell redirects (from #433)
if (pageText.match(/{{(?:redr|this is a redirect|r(?:edirect)?(?:.?cat.*)?[ _]?sh)/i)) {
// Regex inspired by [[User:Kephir/gadgets/sagittarius.js]] ([[Special:PermaLink/831402893]])
var oldTags = pageText.match(/(\s*{{[A-Za-z ]+\|)((?:[^|{}]*|{{[^}]*}})+)(}})\s*/i);
pageText = pageText.replace(oldTags[0], oldTags[1] + tagText + oldTags[2] + oldTags[3]);
} else {
// Fold any pre-existing Rcats into taglist and under Rcatshell
var pageTags = pageText.match(/\n{{R(?:edirect)? .*?}}/img);
var oldPageTags = '';
if (pageTags) {
pageTags.forEach(function(pageTag) {
var pageRe = new RegExp(pageTag, 'img');
pageText = pageText.replace(pageRe, '');
oldPageTags += pageTag;
});
}
pageText += '\n{{Redirect category shell|' + tagText + oldPageTags + '\n}}';
}
summaryText += (tags.length > 0 ? ' tag' + (tags.length > 1 ? 's' : '') : '') + ' to redirect';
// avoid truncated summaries
if (summaryText.length > (499 - Twinkle.getPref('summaryAd').length)) {
summaryText = summaryText.replace(/\[\[[^|]+\|([^\]]+)\]\]/g, '$1');
}
pageobj.setPageText(pageText);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages'));
pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor'));
pageobj.setCreateOption('nocreate');
pageobj.save();
if (params.patrol) {
pageobj.triage();
}
},
file: function friendlytagCallbacksFile(pageobj) {
var text = pageobj.getPageText();
var params = pageobj.getCallbackParameters();
var summary = 'Adding ';
// Add maintenance tags
if (params.tags.length) {
var tagtext = '', currentTag;
$.each(params.tags, function(k, tag) {
// when other commons-related tags are placed, remove "move to Commons" tag
if (['Keep local', 'subst:ncd', 'Do not move to Commons_reason', 'Do not move to Commons',
'Now Commons'].indexOf(tag) !== -1) {
text = text.replace(/\{\{(mtc|(copy |move )?to ?commons|move to wikimedia commons|copy to wikimedia commons)[^}]*\}\}/gi, '');
}
currentTag = '{{' + (tag === 'Do not move to Commons_reason' ? 'Do not move to Commons' : tag);
switch (tag) {
case 'subst:ncd':
if (params.ncdName !== '') {
currentTag += '|1=' + params.ncdName;
}
break;
case 'Keep local':
if (params.keeplocalName !== '') {
currentTag += '|1=' + params.keeplocalName;
}
break;
case 'Rename media':
if (params.renamemediaNewname !== '') {
currentTag += '|1=' + params.renamemediaNewname;
}
if (params.renamemediaReason !== '') {
currentTag += '|2=' + params.renamemediaReason;
}
break;
case 'Cleanup image':
currentTag += '|1=' + params.cleanupimageReason;
break;
case 'Image-Poor-Quality':
currentTag += '|1=' + params.ImagePoorQualityReason;
break;
case 'Low quality chem':
currentTag += '|1=' + params.lowQualityChemReason;
break;
case 'Vector version available':
text = text.replace(/\{\{((convert to |convertto|should be |shouldbe|to)?svg|badpng|vectorize)[^}]*\}\}/gi, '');
/* falls through */
case 'PNG version available':
/* falls through */
case 'Obsolete':
currentTag += '|1=' + params[tag.replace(/ /g, '_') + 'File'];
break;
case 'Do not move to Commons_reason':
currentTag += '|reason=' + params.DoNotMoveToCommons;
break;
case 'subst:orfurrev':
// remove {{non-free reduce}} and redirects
text = text.replace(/\{\{\s*(Template\s*:\s*)?(Non-free reduce|FairUseReduce|Fairusereduce|Fair Use Reduce|Fair use reduce|Reduce size|Reduce|Fair-use reduce|Image-toobig|Comic-ovrsize-img|Non-free-reduce|Nfr|Smaller image|Nonfree reduce)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/ig, '');
currentTag += '|date={{subst:date}}';
break;
case 'Copy to Commons':
currentTag += '|human=' + mw.config.get('wgUserName');
break;
case 'Should be SVG':
currentTag += '|' + params.svgCategory;
break;
default:
break; // don't care
}
currentTag += '}}\n';
tagtext += currentTag;
summary += '{{' + tag + '}}, ';
});
if (!tagtext) {
pageobj.getStatusElement().warn('User canceled operation; nothing to do');
return;
}
text = tagtext + text;
}
pageobj.setPageText(text);
pageobj.setEditSummary(summary.substring(0, summary.length - 2) + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages'));
pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor'));
pageobj.setCreateOption('nocreate');
pageobj.save();
if (params.patrol) {
pageobj.triage();
}
}
};
Twinkle.tag.callback.evaluate = function friendlytagCallbackEvaluate(e) {
var form = e.target;
var params = {};
if (form.patrolPage) {
params.patrol = form.patrolPage.checked;
}
// Don't return null if there aren't any available tags
params.tags = form.getChecked(Twinkle.tag.mode + 'Tags') || [];
// Save values of input fields into params object. This works as quickform input
// fields within subgroups of elements with name 'articleTags' (say) have their
// name attribute as 'articleTags.' + name of the subgroup element
var name_prefix = Twinkle.tag.mode + 'Tags.';
$(form).find("[name^='" + name_prefix + "']:not(div)").each(function(idx, el) {
// el are the HTMLInputElements, el.name gives the name attribute
params[el.name.slice(name_prefix.length)] =
el.type === 'checkbox' ? form[el.name].checked : form[el.name].value;
});
switch (Twinkle.tag.mode) {
case 'article':
params.tagsToRemove = form.getUnchecked('alreadyPresentArticleTags') || [];
params.tagsToRemain = form.getChecked('alreadyPresentArticleTags') || [];
params.reason = form.reason.value.trim();
params.group = form.group.checked;
// Validation
if ((params.tags.indexOf('Merge') !== -1) || (params.tags.indexOf('Merge from') !== -1) ||
(params.tags.indexOf('Merge to') !== -1)) {
if (((params.tags.indexOf('Merge') !== -1) + (params.tags.indexOf('Merge from') !== -1) +
(params.tags.indexOf('Merge to') !== -1)) > 1) {
alert('Please select only one of {{merge}}, {{merge from}}, and {{merge to}}. If several merges are required, use {{merge}} and separate the article names with pipes (although in this case Twinkle cannot tag the other articles automatically).');
return;
}
if (!params.mergeTarget) {
alert('Please specify the title of the other article for use in the merge template.');
return;
}
if ((params.mergeTagOther || params.mergeReason) && params.mergeTarget.indexOf('|') !== -1) {
alert('Tagging multiple articles in a merge, and starting a discussion for multiple articles, is not supported at the moment. Please turn off "tag other article", and/or clear out the "reason" box, and try again.');
return;
}
}
if ((params.tags.indexOf('Not English') !== -1) && (params.tags.indexOf('Rough translation') !== -1)) {
alert('Please select only one of {{not English}} and {{rough translation}}.');
return;
}
if (params.tags.indexOf('History merge') !== -1 && params.histmergeOriginalPage.trim() === '') {
alert('You must specify a page to be merged for the {{history merge}} tag.');
return;
}
if (params.tags.indexOf('Cleanup') !== -1 && params.cleanup.trim() === '') {
alert('You must specify a reason for the {{cleanup}} tag.');
return;
}
if (params.tags.indexOf('Expand language') !== -1 && params.expandLanguageLangCode.trim() === '') {
alert('You must specify language code for the {{expand language}} tag.');
return;
}
break;
case 'file':
if (params.tags.indexOf('Cleanup image') !== -1 && params.cleanupimageReason === '') {
alert('You must specify a reason for the cleanup tag.');
return;
}
if (params.tags.indexOf('Image-Poor-Quality') !== -1 && params.ImagePoorQualityReason === '') {
alert('You must specify a reason for the {{Image-Poor-Quality}} tag');
return;
}
if (params.tags.indexOf('Low Quality Chem') !== -1 && params.lowQualityChemReason === '') {
alert('You must specify a reason for the {{Low Quality Chem}} tag');
return;
}
if ((params.tags.indexOf('Obsolete') !== -1 && params.ObsoleteFile === '') ||
(params.tags.indexOf('PNG version available') !== -1 && params.PNG_version_availableFile === '') ||
(params.tags.indexOf('Vector version available') !== -1 && params.Vector_version_availableFile === '')
) {
alert('You must specify the replacement file name for a tag in the Replacement tags list');
return;
}
if (params.tags.indexOf('Do not move to Commons_reason') !== -1 && params.DoNotMoveToCommons === '') {
alert('You must specify a reason for the {{Do not move to Commons}} tag');
return;
}
break;
case 'redirect':
break;
default:
alert('Twinkle.tag: unknown mode ' + Twinkle.tag.mode);
break;
}
// File/redirect: return if no tags selected
// Article: return if no tag is selected and no already present tag is deselected
if (params.tags.length === 0 && (Twinkle.tag.mode !== 'article' || params.tagsToRemove.length === 0)) {
alert('You must select at least one tag!');
return;
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = Morebits.pageNameNorm;
Morebits.wiki.actionCompleted.notice = 'Tagging complete, reloading article in a few seconds';
if (Twinkle.tag.mode === 'redirect') {
Morebits.wiki.actionCompleted.followRedirect = false;
}
var wikipedia_page = new Morebits.wiki.page(Morebits.pageNameNorm, 'Tagging ' + Twinkle.tag.mode);
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.tag.callbacks[Twinkle.tag.mode]);
};
})(jQuery);
// </nowiki>
/* ===== twinklediff.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklediff.js: Diff module
****************************************
* Mode of invocation: Tab on non-diff pages ("Last"); tabs on diff pages ("Since", "Since mine", "Current")
* Active on: Existing non-special pages
*/
Twinkle.diff = function twinklediff() {
if (mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId')) {
return;
}
Twinkle.addPortletLink(mw.util.getUrl(mw.config.get('wgPageName'), {diff: 'cur', oldid: 'prev'}), 'آخرین ویرایش', 'tw-lastdiff', 'نمایش تفاوت در آخرین ویرایش'); // localized
// Show additional tabs only on diff pages
if (mw.util.getParamValue('diff')) {
Twinkle.addPortletLink(function() {
Twinkle.diff.evaluate(false);
}, 'تفاوت از نفر قبلی', 'tw-since', 'نمایش تفاوت بین آخرین تفاوت به ویرایش کاربر قبل از آن'); // localized
Twinkle.addPortletLink(function() {
Twinkle.diff.evaluate(true);
}, 'تفاوت از ویرایش من', 'tw-sincemine', 'نمایش تفاوت بین آخرین تفاوت و آخرین ویرایش من'); // localized
var oldid = /oldid=(.+)/.exec($('#mw-diff-ntitle1').find('strong a').first().attr('href'))[1];
Twinkle.addPortletLink(mw.util.getUrl(mw.config.get('wgPageName'), {diff: 'cur', oldid: oldid}), 'تفاوت با نسخهٔ فعلی', 'tw-curdiff', 'نمایش تفاوت با نسخهٔ فعلی'); // localized
}
};
Twinkle.diff.evaluate = function twinklediffEvaluate(me) {
var user;
if (me) {
user = mw.config.get('wgUserName');
} else {
var node = document.getElementById('mw-diff-ntitle2');
if (!node) {
// nothing to do?
return;
}
user = $(node).find('a').first().text();
}
var query = {
'prop': 'revisions',
'action': 'query',
'titles': mw.config.get('wgPageName'),
'rvlimit': 1,
'rvprop': [ 'ids', 'user' ],
'rvstartid': mw.config.get('wgCurRevisionId') - 1, // i.e. not the current one
'rvuser': user
};
Morebits.status.init(document.getElementById('mw-content-text'));
var wikipedia_api = new Morebits.wiki.api('گرفتن اطلاعات مشارکتکنندهٔ اولیه', query, Twinkle.diff.callbacks.main); // localized
wikipedia_api.params = { user: user };
wikipedia_api.post();
};
Twinkle.diff.callbacks = {
main: function(self) {
var xmlDoc = self.responseXML;
var revid = $(xmlDoc).find('rev').attr('revid');
if (!revid) {
self.statelem.error('هیچ نسخهٔ قبلیتر مناسبی پیدا نشد، یا ' + self.params.user + 'تنها مشارکتکننده بودهاست. پروسه متوقف شد.'); // localized
return;
}
window.location = mw.util.getUrl(mw.config.get('wgPageName'), {
diff: mw.config.get('wgCurRevisionId'),
oldid: revid
});
}
};
})(jQuery);
// </nowiki>
/* ===== twinkleunlink.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinkleunlink.js: Unlink module
****************************************
* Mode of invocation: Tab ("Unlink")
* Active on: Non-special pages, except Wikipedia:Sandbox
*/
Twinkle.unlink = function twinkleunlink() {
if (mw.config.get('wgNamespaceNumber') < 0 || mw.config.get('wgPageName') === 'ویکیواژه:صفحه تمرین' || // localized
// Restrict to extended confirmed users (see #428)
(!Morebits.userIsInGroup('extendedconfirmed') && !Morebits.userIsSysop)) {
return;
}
Twinkle.addPortletLink(Twinkle.unlink.callback, 'پیوندزدایی', 'tw-unlink', 'زدودن پیوندهای درونویکی'); // localized
};
Twinkle.unlink.getChecked2 = function twinkleunlinkGetChecked2(nodelist) {
if (!(nodelist instanceof NodeList) && !(nodelist instanceof HTMLCollection)) {
return nodelist.checked ? [ nodelist.values ] : [];
}
var result = [];
for (var i = 0; i < nodelist.length; ++i) {
if (nodelist[i].checked) {
result.push(nodelist[i].values);
}
}
return result;
};
// the parameter is used when invoking unlink from admin speedy
Twinkle.unlink.callback = function(presetReason) {
var Window = new Morebits.simpleWindow(600, 440);
Window.setTitle('زدودن پیوندها' + (mw.config.get('wgNamespaceNumber') === 6 ? ' و کاربردهای پرونده' : '')); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#unlink'); // localized
var form = new Morebits.quickForm(Twinkle.unlink.callback.evaluate);
// prepend some basic documentation
var node1 = Morebits.htmlNode('code', '[[' + Morebits.pageNameNorm + '|متن پیوند]]'); // localized
var node2 = Morebits.htmlNode('code', 'متن پیوند'); // localized
node1.style.fontFamily = node2.style.fontFamily = 'monospace';
node1.style.fontStyle = node2.style.fontStyle = 'normal';
form.append({
type: 'div',
style: 'margin-bottom: 0.5em',
label: [
'این ابزار اجازه میدهد که پیوندهای ورودی به این صفحه را از صفحههای دیگر بزدایید' + // localized
(mw.config.get('wgNamespaceNumber') === 6 ? '، و/یا کاربردهای این پرونده در صفحههای دیگر را با دستور <!-- --> پنهان کنید' : '') + // localized
'. برای مثال، ', // localized
node1,
' تبدیل میشود به ', // localized
node2,
'. از این ابزار با احتیاط استفاده کنید.' // localized
]
});
form.append({
type: 'input',
name: 'reason',
label: 'دلیل: ', // localized
value: presetReason ? presetReason : '',
size: 60
});
var query;
if (mw.config.get('wgNamespaceNumber') === 6) { // File:
query = {
'action': 'query',
'list': [ 'backlinks', 'imageusage' ],
'bltitle': mw.config.get('wgPageName'),
'iutitle': mw.config.get('wgPageName'),
'bllimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
'iulimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
'blnamespace': Twinkle.getPref('unlinkNamespaces'),
'iunamespace': Twinkle.getPref('unlinkNamespaces'),
'rawcontinue': true
};
} else {
query = {
'action': 'query',
'list': 'backlinks',
'bltitle': mw.config.get('wgPageName'),
'blfilterredir': 'nonredirects',
'bllimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
'blnamespace': Twinkle.getPref('unlinkNamespaces'),
'rawcontinue': true
};
}
var wikipedia_api = new Morebits.wiki.api('گرفتن پیوندها', query, Twinkle.unlink.callbacks.display.backlinks); // localized
wikipedia_api.params = { form: form, Window: Window, image: mw.config.get('wgNamespaceNumber') === 6 };
wikipedia_api.post();
var root = document.createElement('div');
root.style.padding = '15px'; // just so it doesn't look broken
Morebits.status.init(root);
wikipedia_api.statelem.status('در حال بارگیری...'); // localized
Window.setContent(root);
Window.display();
};
Twinkle.unlink.callback.evaluate = function twinkleunlinkCallbackEvaluate(event) {
var reason = event.target.reason.value;
if (!reason) {
alert('شما باید دلیل زدودن پیوندها را مشخص کنید.'); // localized
return;
}
var backlinks = [], imageusage = [];
if (event.target.backlinks) {
backlinks = Twinkle.unlink.getChecked2(event.target.backlinks);
}
if (event.target.imageusage) {
imageusage = Twinkle.unlink.getChecked2(event.target.imageusage);
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(event.target);
var pages = Morebits.array.uniq(backlinks.concat(imageusage));
var unlinker = new Morebits.batchOperation('زدودن پیوندهای ورودی' + (imageusage ? ' و موارد استفاده از پرونده' : '')); // localized
unlinker.setOption('preserveIndividualStatusLines', true);
unlinker.setPageList(pages);
var params = { reason: reason, unlinker: unlinker };
unlinker.run(function(pageName) {
var wikipedia_page = new Morebits.wiki.page(pageName, 'زدودن پیوندها در صفحهٔ «' + pageName + '»'); // localized
wikipedia_page.setBotEdit(true); // unlink considered a floody operation
var innerParams = $.extend({}, params);
innerParams.doBacklinks = backlinks && backlinks.indexOf(pageName) !== -1;
innerParams.doImageusage = imageusage && imageusage.indexOf(pageName) !== -1;
wikipedia_page.setCallbackParameters(innerParams);
wikipedia_page.load(Twinkle.unlink.callbacks.unlinkBacklinks);
});
};
Twinkle.unlink.callbacks = {
display: {
backlinks: function twinkleunlinkCallbackDisplayBacklinks(apiobj) {
var xmlDoc = apiobj.responseXML;
var havecontent = false;
var list, namespaces, i;
if (apiobj.params.image) {
var imageusage = $(xmlDoc).find('query imageusage iu');
list = [];
for (i = 0; i < imageusage.length; ++i) {
var usagetitle = imageusage[i].getAttribute('title');
list.push({ label: usagetitle, value: usagetitle, checked: true });
}
if (!list.length) {
apiobj.params.form.append({ type: 'div', label: 'هیچ موردی استفادهای از پرونده یافت نشد.' }); // localized
} else {
apiobj.params.form.append({ type: 'header', label: 'کاربردهای پرونده' }); // localized
namespaces = [];
$.each(Twinkle.getPref('unlinkNamespaces'), function(k, v) {
namespaces.push(v === '0' ? '(مقاله)' : mw.config.get('wgFormattedNamespaces')[v]); // localized
});
apiobj.params.form.append({
type: 'div',
label: 'فضای نام انتخابشده: ' + namespaces.join(', '), // localized
tooltip: 'با تنظیمات توینکل (در [[وو:توینکل تر]]) میتوانید این مورد را تغییر دهید' // localized
});
if ($(xmlDoc).find('query-continue').length) {
apiobj.params.form.append({
type: 'div',
label: 'اولین ' + mw.language.convertNumber(list.length.toString()) + ' کاربرد پرونده نشان داده شدهاست.' // localized
});
}
apiobj.params.form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'imageusage')).prop('checked', true);
}
});
apiobj.params.form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'imageusage')).prop('checked', false);
}
});
apiobj.params.form.append({
type: 'checkbox',
name: 'imageusage',
list: list
});
havecontent = true;
}
}
var backlinks = $(xmlDoc).find('query backlinks bl');
if (backlinks.length > 0) {
list = [];
for (i = 0; i < backlinks.length; ++i) {
var title = backlinks[i].getAttribute('title');
list.push({ label: title, value: title, checked: true });
}
apiobj.params.form.append({ type: 'header', label: 'پیوندهای ورودی' });
namespaces = [];
$.each(Twinkle.getPref('unlinkNamespaces'), function(k, v) {
namespaces.push(v === '0' ? '(مقاله)' : mw.config.get('wgFormattedNamespaces')[v]); // localized
});
apiobj.params.form.append({
type: 'div',
label: 'فضاهای نام انتخابشده: ' + namespaces.join(', '), // localized
tooltip: 'با تنظیمات توینکل (در [[وو:توینکل تر]]) میتوانید این مورد را تغییر دهید' // localized
});
if ($(xmlDoc).find('query-continue').length) {
apiobj.params.form.append({
type: 'div',
label: 'اولین ' + mw.language.convertNumber(list.length.toString()) + ' پیوندهای ورودی نشان داده شدهاند.' // localized
});
}
apiobj.params.form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'backlinks')).prop('checked', true);
}
});
apiobj.params.form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'backlinks')).prop('checked', false);
}
});
apiobj.params.form.append({
type: 'checkbox',
name: 'backlinks',
list: list
});
havecontent = true;
} else {
apiobj.params.form.append({ type: 'div', label: 'پیوند ورودی یافت نشد.' }); // localized
}
if (havecontent) {
apiobj.params.form.append({ type: 'submit' });
}
var result = apiobj.params.form.render();
apiobj.params.Window.setContent(result);
Morebits.checkboxShiftClickSupport($("input[name='imageusage']", result));
Morebits.checkboxShiftClickSupport($("input[name='backlinks']", result));
}
},
unlinkBacklinks: function twinkleunlinkCallbackUnlinkBacklinks(pageobj) {
var oldtext = pageobj.getPageText();
var params = pageobj.getCallbackParameters();
var wikiPage = new Morebits.wikitext.page(oldtext);
var summaryText = '', warningString = false;
var text;
// remove image usages
if (params.doImageusage) {
wikiPage.commentOutImage(mw.config.get('wgTitle'), 'پنهان شد'); // localized
text = wikiPage.getText();
// did we actually make any changes?
if (text === oldtext) {
warningString = 'کاربردی از پرونده'; // localized
} else {
summaryText = 'پنهان کردن کاربردهای پرونده'; // localized
oldtext = text;
}
}
// remove backlinks
if (params.doBacklinks) {
wikiPage.removeLink(Morebits.pageNameNorm);
text = wikiPage.getText();
// did we actually make any changes?
if (text === oldtext) {
warningString = warningString ? 'پیوند ورودی یا کاربردی از پرونده' : 'پیوند ورودی'; // localized
} else {
summaryText = (summaryText ? summaryText + ' / ' : '') + 'زدودن پیوندها به'; // localized
oldtext = text;
}
}
if (warningString) {
// nothing to do!
pageobj.getStatusElement().error("هیچ " + warningString + ' در صفحه یافت نشد.'); // localized
params.unlinker.workerFailure(pageobj);
return;
}
pageobj.setPageText(text);
pageobj.setEditSummary(summaryText + ' «' + Morebits.pageNameNorm + '»: ' + params.reason + '.' + Twinkle.getPref('summaryAd')); // localized
pageobj.setCreateOption('nocreate');
pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
}
};
})(jQuery);
// </nowiki>
/* ===== twinklefluff.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklefluff.js: Revert/rollback module
****************************************
* Mode of invocation: Links on contributions, recent changes, history, and diff pages
* Active on: Diff pages, history pages, Special:RecentChanges(Linked),
and Special:Contributions
*/
/**
Twinklefluff revert and antivandalism utility
*/
Twinkle.fluff = function twinklefluff() {
// A list of usernames, usually only bots, that vandalism revert is jumped over; that is,
// if vandalism revert was chosen on such username, then its target is on the revision before.
// This is for handling quick bots that makes edits seconds after the original edit is made.
// This only affects vandalism rollback; for good faith rollback, it will stop, indicating a bot
// has no faith, and for normal rollback, it will rollback that edit.
Twinkle.fluff.whiteList = [
// localized
];
if (mw.config.get('wgIsProbablyEditable') && mw.config.get('wgNamespaceNumber') !== 2600) { // localized [exclude Flow pages]
// Only proceed if the user can actually edit the page
// in question (ignored for contributions, see #632).
// wgIsProbablyEditable should take care of
// namespace/contentModel restrictions as well as
// explicit protections; it won't take care of
// cascading or TitleBlacklist restrictions
if (mw.config.get('wgDiffNewId') || mw.config.get('wgDiffOldId')) { // wgDiffOldId included for clarity in if else loop [[phab:T214985]]
mw.hook('wikipage.diff').add(function () { // Reload alongside the revision slider
Twinkle.fluff.addLinks.diff();
});
} else if (mw.config.get('wgAction') === 'view' && mw.config.get('wgCurRevisionId') !== mw.config.get('wgRevisionId')) {
Twinkle.fluff.addLinks.oldid();
} else if (mw.config.get('wgAction') === 'history') {
Twinkle.fluff.addLinks.history();
}
} else if (mw.config.get('wgNamespaceNumber') === -1) {
Twinkle.fluff.skipTalk = !Twinkle.getPref('openTalkPageOnAutoRevert');
Twinkle.fluff.rollbackInPlace = Twinkle.getPref('rollbackInPlace');
if (mw.config.get('wgCanonicalSpecialPageName') === 'Contributions') {
Twinkle.fluff.addLinks.contributions();
} else if (mw.config.get('wgCanonicalSpecialPageName') === 'Recentchanges' || mw.config.get('wgCanonicalSpecialPageName') === 'Recentchangeslinked') {
// Reload with recent changes updates
// structuredChangeFilters.ui.initialized is just on load
mw.hook('wikipage.content').add(function(item) {
if (item.is('div')) {
Twinkle.fluff.addLinks.recentchanges();
}
});
}
}
};
Twinkle.fluff.skipTalk = null;
Twinkle.fluff.rollbackInPlace = null;
// Consolidated construction of fluff links
Twinkle.fluff.linkBuilder = {
spanTag: function(color, content) {
var span = document.createElement('span');
span.style.color = color;
span.appendChild(document.createTextNode(content));
return span;
},
buildLink: function(color, text) {
var link = document.createElement('a');
link.appendChild(Twinkle.fluff.linkBuilder.spanTag('Black', '['));
link.appendChild(Twinkle.fluff.linkBuilder.spanTag(color, text));
link.appendChild(Twinkle.fluff.linkBuilder.spanTag('Black', ']'));
link.href = '#';
return link;
},
/**
* @param {string} vandal - Username of the editor being reverted (required)
* @param {boolean} inline - True to create two links in a span, false
* to create three links in a div (optional)
* @param {number|string} [rev=wgCurRevisionId] - Revision ID being reverted (optional)
* @param {string} [page=wgPageName] - Page being reverted (optional)
*/
rollbackLinks: function(vandal, inline, rev, page) {
var elem = inline ? 'span' : 'div';
var revNode = document.createElement(elem);
rev = parseInt(rev, 10);
if (rev) {
revNode.setAttribute('id', 'tw-revert' + rev);
} else {
revNode.setAttribute('id', 'tw-revert');
}
var normNode = document.createElement('strong');
var vandNode = document.createElement('strong');
var normLink = Twinkle.fluff.linkBuilder.buildLink('SteelBlue', 'برگردانی'); // localiezd
var vandLink = Twinkle.fluff.linkBuilder.buildLink('Red', 'خرابکاری'); // localized
$(normLink).click(function() {
Twinkle.fluff.revert('norm', vandal, rev, page);
Twinkle.fluff.disableLinks(revNode);
});
$(vandLink).click(function() {
Twinkle.fluff.revert('vand', vandal, rev, page);
Twinkle.fluff.disableLinks(revNode);
});
vandNode.appendChild(vandLink);
normNode.appendChild(normLink);
var separator = inline ? ' ' : ' || ';
if (!inline) {
var agfNode = document.createElement('strong');
var agfLink = Twinkle.fluff.linkBuilder.buildLink('DarkOliveGreen', 'برگردانی (فحن)'); // localized
$(agfLink).click(function() {
Twinkle.fluff.revert('agf', vandal, rev, page);
// Twinkle.fluff.disableLinks(revNode); // rollbackInPlace not relevant for any inline situations
});
agfNode.appendChild(agfLink);
revNode.appendChild(agfNode);
}
revNode.appendChild(document.createTextNode(separator));
revNode.appendChild(normNode);
revNode.appendChild(document.createTextNode(separator));
revNode.appendChild(vandNode);
return revNode;
},
// Build [restore this revision] links
restoreThisRevisionLink: function(revisionRef, inline) {
// If not a specific revision number, should be wgDiffNewId/wgDiffOldId/wgRevisionId
revisionRef = typeof revisionRef === 'number' ? revisionRef : mw.config.get(revisionRef);
var elem = inline ? 'span' : 'div';
var revertToRevisionNode = document.createElement(elem);
revertToRevisionNode.setAttribute('id', 'tw-revert-to-' + revisionRef);
revertToRevisionNode.style.fontWeight = 'bold';
var revertToRevisionLink = Twinkle.fluff.linkBuilder.buildLink('SaddleBrown', 'برگردانی به این نسخه'); // localized
$(revertToRevisionLink).click(function() {
Twinkle.fluff.revertToRevision(revisionRef);
});
if (inline) {
revertToRevisionNode.appendChild(document.createTextNode(' '));
}
revertToRevisionNode.appendChild(revertToRevisionLink);
return revertToRevisionNode;
}
};
Twinkle.fluff.addLinks = {
contributions: function() {
// $('sp-contributions-footer-anon-range') relies on the fmbox
// id in [[MediaWiki:Sp-contributions-footer-anon-range]] and
// is used to show rollback/vandalism links for IP ranges
if (mw.config.exists('wgRelevantUserName') || !!$('#sp-contributions-footer-anon-range')[0]) {
// Get the username these contributions are for
var username = mw.config.get('wgRelevantUserName');
if (Twinkle.getPref('showRollbackLinks').indexOf('contribs') !== -1 ||
(mw.config.get('wgUserName') !== username && Twinkle.getPref('showRollbackLinks').indexOf('others') !== -1) ||
(mw.config.get('wgUserName') === username && Twinkle.getPref('showRollbackLinks').indexOf('mine') !== -1)) {
var list = $('#mw-content-text').find('ul li:has(span.mw-uctop):has(.mw-changeslist-diff)');
list.each(function(key, current) {
// revid is also available in the href of both
// .mw-changeslist-date or .mw-changeslist-diff
var page = $(current).find('.mw-contributions-title').text();
current.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks(username, true, current.dataset.mwRevid, page));
});
}
}
},
recentchanges: function() {
if (Twinkle.getPref('showRollbackLinks').indexOf('recent') !== -1) {
// Latest and revertable (not page creations, logs, categorizations, etc.)
var list = $('.mw-changeslist .mw-changeslist-last.mw-changeslist-src-mw-edit');
// Exclude top-level header if "group changes" preference is used
// and find only individual lines or nested lines
list = list.not('.mw-rcfilters-ui-highlights-enhanced-toplevel').find('.mw-changeslist-line-inner, td.mw-enhanced-rc-nested');
list.each(function(key, current) {
var vandal = $(current).find('.mw-userlink').text();
var href = $(current).find('.mw-changeslist-diff').attr('href');
var rev = mw.util.getParamValue('diff', href);
var page = current.dataset.targetPage;
current.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks(vandal, true, rev, page));
});
}
},
history: function() {
if (Twinkle.getPref('showRollbackLinks').indexOf('history') !== -1) {
// All revs
var histList = $('#pagehistory li').toArray();
// On first page of results, so add revert/rollback
// links to the top revision
if (!$('.mw-firstlink').length) {
var first = histList.shift();
var vandal = first.querySelector('.mw-userlink').text;
first.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks(vandal, true));
}
// oldid
histList.forEach(function(rev) {
// From restoreThisRevision, non-transferable
var href = rev.querySelector('.mw-changeslist-date').href;
var oldid = parseInt(mw.util.getParamValue('oldid', href), 10);
rev.appendChild(Twinkle.fluff.linkBuilder.restoreThisRevisionLink(oldid, true));
});
}
},
diff: function() {
// Autofill user talk links on diffs with vanarticle for easy warning, but don't autowarn
var warnFromTalk = function(xtitle) {
var talkLink = $('#mw-diff-' + xtitle + '2 .mw-usertoollinks a').first();
if (talkLink.length) {
var extraParams = 'vanarticle=' + mw.util.rawurlencode(Morebits.pageNameNorm) + '&' + 'noautowarn=true';
// diffIDs for vanarticlerevid
extraParams += '&vanarticlerevid=';
extraParams += xtitle === 'otitle' ? mw.config.get('wgDiffOldId') : mw.config.get('wgDiffNewId');
var href = talkLink.attr('href');
if (href.indexOf('?') === -1) {
talkLink.attr('href', href + '?' + extraParams);
} else {
talkLink.attr('href', href + '&' + extraParams);
}
}
};
// Older revision
warnFromTalk('otitle'); // Add quick-warn link to user talk link
// Don't load if there's a single revision or weird diff (cur on latest)
if (mw.config.get('wgDiffOldId') && (mw.config.get('wgDiffOldId') !== mw.config.get('wgDiffNewId'))) {
// Add a [restore this revision] link to the older revision
var oldTitle = document.getElementById('mw-diff-otitle1').parentNode;
oldTitle.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgDiffOldId'), oldTitle.firstChild);
}
// Newer revision
warnFromTalk('ntitle'); // Add quick-warn link to user talk link
// Add either restore or rollback links to the newer revision
// Don't show if there's a single revision or weird diff (prev on first)
if (document.getElementById('differences-nextlink')) {
// Not latest revision, add [restore this revision] link to newer revision
var newTitle = document.getElementById('mw-diff-ntitle1').parentNode;
newTitle.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgDiffNewId'), newTitle.firstChild);
} else if (Twinkle.getPref('showRollbackLinks').indexOf('diff') !== -1 && mw.config.get('wgDiffOldId') && (mw.config.get('wgDiffOldId') !== mw.config.get('wgDiffNewId') || document.getElementById('differences-prevlink'))) {
var vandal = $('#mw-diff-ntitle2').find('a').first().text();
var ntitle = document.getElementById('mw-diff-ntitle1').parentNode;
ntitle.insertBefore(Twinkle.fluff.linkBuilder.rollbackLinks(vandal), ntitle.firstChild);
}
},
oldid: function() { // Add a [restore this revision] link on old revisions
var title = document.getElementById('mw-revision-info').parentNode;
title.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgRevisionId'), title.firstChild);
}
};
Twinkle.fluff.disableLinks = function disablelinks(parentNode) {
// Array.from not available in IE11 :(
$(parentNode).children().each(function(_ix, node) {
node.innerHTML = node.textContent; // Feels like cheating
$(node).css('font-weight', 'normal').css('color', 'darkgray');
});
};
Twinkle.fluff.revert = function revertPage(type, vandal, rev, page) {
if (mw.util.isIPv6Address(vandal)) {
vandal = Morebits.sanitizeIPv6(vandal);
}
var pagename = page || mw.config.get('wgPageName');
var revid = rev || mw.config.get('wgCurRevisionId');
if (Twinkle.fluff.rollbackInPlace) {
var notifyStatus = document.createElement('span');
mw.notify(notifyStatus, {
autoHide: false,
title: 'برگردانی روی ' + page, // localized
tag: 'twinklefluff_' + rev // Shouldn't be necessary given disableLink
});
Morebits.status.init(notifyStatus);
} else {
Morebits.status.init(document.getElementById('mw-content-text'));
$('#catlinks').remove();
}
var params = {
type: type,
user: vandal,
pagename: pagename,
revid: revid
};
var query = {
'action': 'query',
'prop': ['info', 'revisions', 'flagged'],
'titles': pagename,
'intestactions': 'edit',
'rvlimit': 50, // intentionally limited
'rvprop': [ 'ids', 'timestamp', 'user', 'comment' ],
'curtimestamp': '',
'meta': 'tokens',
'type': 'csrf'
};
var wikipedia_api = new Morebits.wiki.api('دریافت اطلاعات نسخهٔ قدیمیتر', query, Twinkle.fluff.callbacks.main); // localized
wikipedia_api.params = params;
wikipedia_api.post();
};
Twinkle.fluff.revertToRevision = function revertToRevision(oldrev) {
Morebits.status.init(document.getElementById('mw-content-text'));
var query = {
'action': 'query',
'prop': ['info', 'revisions'],
'titles': mw.config.get('wgPageName'),
'rvlimit': 1,
'rvstartid': oldrev,
'rvprop': [ 'ids', 'timestamp', 'user', 'comment' ],
'format': 'xml',
'curtimestamp': '',
'meta': 'tokens',
'type': 'csrf'
};
var wikipedia_api = new Morebits.wiki.api('دریافت اطلاعات نسخهٔ قدیمیتر', query, Twinkle.fluff.callbacks.toRevision); // localized
wikipedia_api.params = { rev: oldrev };
wikipedia_api.post();
};
Twinkle.fluff.userIpLink = function(user) {
return (mw.util.isIPAddress(user) ? '[[Special:Contributions/' : '[[:User:') + user + '|' + user + ']]';
};
Twinkle.fluff.callbacks = {
toRevision: function(apiobj) {
var xmlDoc = apiobj.responseXML;
var lastrevid = parseInt($(xmlDoc).find('page').attr('lastrevid'), 10);
var touched = $(xmlDoc).find('page').attr('touched');
var loadtimestamp = $(xmlDoc).find('api').attr('curtimestamp');
var csrftoken = $(xmlDoc).find('tokens').attr('csrftoken');
var revertToRevID = parseInt($(xmlDoc).find('rev').attr('revid'), 10);
var revertToUser = $(xmlDoc).find('rev').attr('user');
if (revertToRevID !== apiobj.params.rev) {
apiobj.statelem.error('نسخهای که دریافت شد با نسخهای که درخواست شد مطابقت ندارد. برگردانی متوقف شد.'); // localized
return;
}
var optional_summary = prompt('لطفاً دلیل برگردانی را توضیح دهید: ', ''); // padded out to widen prompt in Firefox // localized
if (optional_summary === null) {
apiobj.statelem.error('توسط کاربر متوقف شد.'); // localized
return;
}
var summary = Twinkle.fluff.formatSummary('به نسخهٔ ' + revertToRevID + ' از $USER برگردانده شد', revertToUser, optional_summary); // localized
var query = {
'action': 'edit',
'title': mw.config.get('wgPageName'),
'summary': summary,
'token': csrftoken,
'undo': lastrevid,
'undoafter': revertToRevID,
'basetimestamp': touched,
'starttimestamp': loadtimestamp,
'watchlist': Twinkle.getPref('watchRevertedPages').indexOf('torev') !== -1 ? 'watch' : undefined,
'minor': Twinkle.getPref('markRevertedPagesAsMinor').indexOf('torev') !== -1 ? true : undefined
};
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'برگردانی موفق بود'; // localized
var wikipedia_api = new Morebits.wiki.api('ذخیرهٔ محتوای برگردانی شده', query, Twinkle.fluff.callbacks.complete, apiobj.statelem); // localized
wikipedia_api.params = apiobj.params;
wikipedia_api.post();
},
main: function(apiobj) {
var xmlDoc = apiobj.responseXML;
if (typeof $(xmlDoc).find('actions').attr('edit') === 'undefined') {
self.statelem.error("Unable to edit the page, it's probably protected.");
return;
}
var lastrevid = parseInt($(xmlDoc).find('page').attr('lastrevid'), 10);
var touched = $(xmlDoc).find('page').attr('touched');
var loadtimestamp = $(xmlDoc).find('api').attr('curtimestamp');
var csrftoken = $(xmlDoc).find('tokens').attr('csrftoken');
var lastuser = $(xmlDoc).find('rev').attr('user');
var revs = $(xmlDoc).find('rev');
var statelem = apiobj.statelem;
var params = apiobj.params;
if (revs.length < 1) {
statelem.error('صفحه تنها یک نسخهٔ دیگر دارد در نتیجه برگردانی ممکن نیست.'); // localiezd
return;
}
var top = revs[0];
if (lastrevid < params.revid) {
Morebits.status.error('Error', [ 'شناسهٔ آخرین نسخه که از سرور دریافت شد ', Morebits.htmlNode('strong', lastrevid), ' بود که از شناسهٔ نسخهٔ نمایش یافته کمتر است. این میتواند نشاندهندهٔ آن باشد که نسخهٔ فعلی حذف شدهاست، سرورها تأخیر دارند، یا دادههایی که دریافت شده اشتباه هستند. برگردانی متوقف شد.' ]); // localized
return;
}
var index = 1;
if (params.revid !== lastrevid) {
Morebits.status.warn('Warning', [ 'نسخهٔ آخر ', Morebits.htmlNode('strong', lastrevid), ' است که با نسخهٔ ما مطابقت ندارد', Morebits.htmlNode('strong', params.revid) ]); // loaclized
if (lastuser === params.user) {
switch (params.type) {
case 'vand':
Morebits.status.info('Info', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', params.user), ' ایجاد شدهاست. از آنجا که فرض بر خرابکاری است، برگردانی ادامه مییابد.' ]); // localizde
break;
case 'agf':
Morebits.status.warn('Warning', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', params.user), ' ایجاد شدهاست. از آنجا که فرض بر حسن نیت است، برگردانی متوقف شد چرا که مشکل ممکن است رفع شده باشد.' ]); // localiezd
return;
default:
Morebits.status.warn('Notice', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', params.user), ' ایجاد شدهاست اما برگردانی متوقف شد.' ]); // localized
return;
}
} else if (params.type === 'vand' &&
Twinkle.fluff.whiteList.indexOf(top.getAttribute('user')) !== -1 && revs.length > 1 &&
revs[1].getAttribute('pageId') === params.revid) {
Morebits.status.info('Info', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', lastuser), ' ایجاد شده که یک ربات مورد اعتماد است. نسخهٔ پیش از آن توسط کاربر خرابکار ایجاد شده لذا برگردانی ادامه مییابد.' ]); // localized
index = 2;
} else {
Morebits.status.error('Error', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', lastuser), ' ایجاد شده پس صفحه قاعدتاً باید برگردانی شده باشد؛ برگردانی متوقف شد.']); // localized
return;
}
}
if (Twinkle.fluff.whiteList.indexOf(params.user) !== -1) {
switch (params.type) {
case 'vand':
Morebits.status.info('Info', [ 'درخواست برگردانی خرابکاری برای ویرایش ', Morebits.htmlNode('strong', params.user), ' داده شده بود. از آنجا که این حساب، یک ربات مورد اعتماد است، فرض شد که منظور شما برگردانی خرابکاری کاربری بود که ویرایش قبل از ربات را انجام داده بود.' ]); // localized
index = 2;
params.user = revs[1].getAttribute('user');
break;
case 'agf':
Morebits.status.warn('Notice', [ 'درخواست برگردانی ویرایش ', Morebits.htmlNode('strong', params.user), ' با فرض حسن نیت داده شده بود. این حساب، یک ربات مورد اعتماد است در نتیجه برگردانی متوقف شد.' ]); // localized
return;
case 'norm':
/* falls through */
default:
var cont = confirm('برگردانی عادی انتخاب شده بود، اما بیشتر تغییرات توسط یک ربات مورد اعتماد (' + params.user + ') انجام شدهاست. آیا میخواهید به نسخهٔ پیش از تغییرات ربات برگردانی کنید؟'); // localized
if (cont) {
Morebits.status.info('Info', [ 'درخواست برگردانی عادی ویرایشهای ', Morebits.htmlNode('strong', params.user), ' داده شده بود. این حساب، یک ربات مورد اعتماد است و به خواست شما، برگردانی به نسخهٔ پیش از ویرایشهای ربات انجام شد.' ]); // localized
index = 2;
params.user = revs[1].getAttribute('user');
} else {
Morebits.status.warn('Notice', [ 'درخواست برگردانی عادی ویرایشهای ', Morebits.htmlNode('strong', params.user), ' داده شدهبود. این حساب، یک ربات مورد اعتماد است اما به خواست شما ویرایشهایش برگردانی شد.' ]); // localized
}
break;
}
}
var found = false;
var count = 0;
for (var i = index; i < revs.length; ++i) {
++count;
if (revs[i].getAttribute('user') !== params.user) {
found = i;
break;
}
}
if (!found) {
statelem.error([ 'نسخهٔ قدیمیتری یافت نشد. شاید ', Morebits.htmlNode('strong', params.user), ' تنها کاربری است که در صفحه مشارکت کرده، یا بیش از ' + mw.language.convertNumber(Twinkle.getPref('revertMaxRevisions')) + ' ویرایش پیاپی در صفحه انجام دادهاست.' ]); // localized
return;
}
if (!count) {
Morebits.status.error('Error', 'از آنجا که برگردانی ویرایشهای خنثی ممکن نیست، برگردانی متوقف شد. ممکن است ویرایش پیشتر برگردانی شده باشد اگر چه شناسهٔ ویرایش هنوز همان باشد.'); // localized
return;
}
var good_revision = revs[found];
var userHasAlreadyConfirmedAction = false;
if (params.type !== 'vand' && count > 1) {
if (!confirm(params.user + ' به طور پیاپی ' + mw.language.convertNumber(count) + ' ویرایش روی صفحه کردهاست. آیا میخواهید همهٔ آنها را برگردانی کنید؟')) { // localized
Morebits.status.info('Notice', 'برگردانی متوقف شد.'); // localized
return;
}
userHasAlreadyConfirmedAction = true;
}
params.count = count;
params.goodid = good_revision.getAttribute('revid');
params.gooduser = good_revision.getAttribute('user');
statelem.status([ 'نسخهٔ ', Morebits.htmlNode('strong', params.goodid), ' که ', Morebits.htmlNode('strong', mw.language.convertNumber(count)), ' نسخه پیشتر توسط ', Morebits.htmlNode('strong', params.gooduser) , ' ثبت شده بود']); // localized
var summary, extra_summary;
switch (params.type) {
case 'agf':
extra_summary = prompt('توضیح اختیاری برای خلاصه ویرایش: ', ''); // padded out to widen prompt in Firefox // localized
if (extra_summary === null) {
statelem.error('توسط کاربر متوقف شد.'); // localized
return;
}
userHasAlreadyConfirmedAction = true;
summary = Twinkle.fluff.formatSummary('ویرایشهای دارای [[وو:فحن|حسن نیت]] $USER برگردانی شد', params.user, extra_summary); // localized
break;
case 'vand':
summary = mw.language.convertNumber(params.count) + (params.count > 1 ? ' ویرایش' : ' ویرایش') + ' از [[Special:Contributions/' + // localized
params.user + '|' + params.user + ']] ([[User talk:' + params.user + '|بحث]]) به آخرین نسخه توسط ' + // localized
params.gooduser + ' برگردانی شد' + Twinkle.getPref('summaryAd'); // localized
break;
case 'norm':
/* falls through */
default:
if (Twinkle.getPref('offerReasonOnNormalRevert')) {
extra_summary = prompt('توضیح اختیاری برای خلاصه ویرایش: ', ''); // padded out to widen prompt in Firefox // localized
if (extra_summary === null) {
statelem.error('توسط کاربر متوقف شد.'); // localized
return;
}
userHasAlreadyConfirmedAction = true;
}
summary = Twinkle.fluff.formatSummary(mw.language.convertNumber(params.count) + (params.count > 1 ? ' ویرایش' : ' ویرایش') + // localized
' $USER برگردانی شد', params.user, extra_summary); // localized
break;
}
if (Twinkle.getPref('confirmOnFluff') && !userHasAlreadyConfirmedAction && !confirm('برگردانی صفحه: آیا مطمئن هستید؟')) { // localized
statelem.error('توسط کاربر متوقف شد.'); // localized
return;
}
// Decide whether to notify the user on success
if (!Twinkle.fluff.skipTalk && Twinkle.getPref('openTalkPage').indexOf(params.type) !== -1 &&
mw.config.get('wgUserName') !== params.user) {
params.notifyUser = true;
}
// figure out whether we need to/can review the edit
var $flagged = $(xmlDoc).find('flagged');
if ((Morebits.userIsInGroup('reviewer') || Morebits.userIsSysop) &&
$flagged.length &&
$flagged.attr('stable_revid') >= params.goodid &&
$flagged.attr('pending_since')) {
params.reviewRevert = true;
params.csrftoken = csrftoken;
}
var query = {
'action': 'edit',
'title': params.pagename,
'summary': summary,
'token': csrftoken,
'undo': lastrevid,
'undoafter': params.goodid,
'basetimestamp': touched,
'starttimestamp': loadtimestamp,
'watchlist': Twinkle.getPref('watchRevertedPages').indexOf(params.type) !== -1 ? 'watch' : undefined,
'minor': Twinkle.getPref('markRevertedPagesAsMinor').indexOf(params.type) !== -1 ? true : undefined
};
if (!Twinkle.fluff.rollbackInPlace) {
Morebits.wiki.actionCompleted.redirect = params.pagename;
}
Morebits.wiki.actionCompleted.notice = 'برگردانی موفق بود'; // localized
var wikipedia_api = new Morebits.wiki.api('در حال ذخیره کردن محتوای برگردانی شده', query, Twinkle.fluff.callbacks.complete, statelem); // localized
wikipedia_api.params = params;
wikipedia_api.post();
},
complete: function (apiobj) {
// TODO Most of this is copy-pasted from Morebits.wiki.page#fnSaveSuccess. Unify it
var xml = apiobj.getXML();
var $edit = $(xml).find('edit');
if ($(xml).find('captcha').length > 0) {
apiobj.statelem.error('برگردانی موفق نبود، چون سرور ویکی درخواست کرد که شما یک کپچا پر کنید.'); // localized
} else if ($edit.attr('nochange') === '') {
apiobj.statelem.error('نسخهای که به آن برگردانی میکنیم با نسخهٔ فعلی یکسان است؛ برگردانی متوقف شد.'); // localized
} else {
apiobj.statelem.info('done');
var params = apiobj.params;
if (params.notifyUser) { // Only from main, not from toRevision
Morebits.status.info('Info', [ 'باز کردن فرم ویرایش صفحهٔ بحث کاربر ', Morebits.htmlNode('strong', params.user) ]); // localized
var windowQuery = {
'title': 'User talk:' + params.user,
'action': 'edit',
'preview': 'yes',
'vanarticle': params.pagename.replace(/_/g, ' '),
'vanarticlerevid': params.revid,
'vanarticlegoodrevid': params.goodid,
'type': params.type,
'count': params.count
};
switch (Twinkle.getPref('userTalkPageMode')) {
case 'tab':
window.open(mw.util.getUrl('', windowQuery), '_blank');
break;
case 'blank':
window.open(mw.util.getUrl('', windowQuery), '_blank',
'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800');
break;
case 'window':
/* falls through */
default:
window.open(mw.util.getUrl('', windowQuery),
window.name === 'twinklewarnwindow' ? '_blank' : 'twinklewarnwindow',
'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800');
break;
}
}
// review the revert, if needed
if (apiobj.params.reviewRevert) {
var query = {
'action': 'review',
'revid': $edit.attr('newrevid'),
'token': apiobj.params.csrftoken,
'comment': Twinkle.getPref('summaryAd').trim()
};
var wikipedia_api = new Morebits.wiki.api('پذیرش خودکار تغییرات شما', query); // localized
wikipedia_api.post();
}
}
}
};
// builtInString should contain the string "$USER", which will be replaced
// by an appropriate user link
Twinkle.fluff.formatSummary = function(builtInString, userName, userString) {
var result = builtInString;
// append user's custom reason
if (userString) {
result += ': ' + Morebits.string.toUpperCaseFirstChar(userString);
}
result += Twinkle.getPref('summaryAd');
// find number of UTF-8 bytes the resulting string takes up, and possibly add
// a contributions or contributions+talk link if it doesn't push the edit summary
// over the 255-byte limit
var resultLen = unescape(encodeURIComponent(result.replace('$USER', ''))).length;
var contribsLink = '[[Special:Contributions/' + userName + '|' + userName + ']]';
var contribsLen = unescape(encodeURIComponent(contribsLink)).length;
if (resultLen + contribsLen <= 255) {
var talkLink = ' ([[User talk:' + userName + '|بحث]])'; // localized
if (resultLen + contribsLen + unescape(encodeURIComponent(talkLink)).length <= 255) {
result = Morebits.string.safeReplace(result, '$USER', contribsLink + talkLink);
} else {
result = Morebits.string.safeReplace(result, '$USER', contribsLink);
}
} else {
result = Morebits.string.safeReplace(result, '$USER', userName);
}
return result;
};
})(jQuery);
// </nowiki>
/* ===== twinklebatchdelete.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklebatchdelete.js: Batch delete module (sysops only)
****************************************
* Mode of invocation: Tab ("D-batch")
* Active on: Existing non-articles, and Special:PrefixIndex
*/
Twinkle.batchdelete = function twinklebatchdelete() {
if (
Morebits.userIsSysop && (
(mw.config.get('wgCurRevisionId') && mw.config.get('wgNamespaceNumber') > 0) ||
mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex'
)
) {
Twinkle.addPortletLink(Twinkle.batchdelete.callback, 'حذف دستهجمعی', 'tw-batch', 'حذف صفحههایی که در این صفحه پیوند دارند یا در این رده قرار دارند'); // localized
}
};
Twinkle.batchdelete.unlinkCache = {};
// Has the subpages list been loaded?
var subpagesLoaded;
Twinkle.batchdelete.callback = function twinklebatchdeleteCallback() {
subpagesLoaded = false;
var Window = new Morebits.simpleWindow(600, 400);
Window.setTitle('حذف دستهجمعی'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#batchdelete'); // localized
var form = new Morebits.quickForm(Twinkle.batchdelete.callback.evaluate);
form.append({
type: 'checkbox',
list: [
{
label: 'حذف صفحهها', // localized
name: 'delete_page',
value: 'delete',
checked: true,
subgroup: {
type: 'checkbox',
list: [
{
label: 'حذف صفحهٔ بحث مرتبط (به جز صفحههای بحث کاربر)', // localized
name: 'delete_talk',
value: 'delete_talk',
checked: true
},
{
label: 'حذف تغییرمسیرها به صفحههای انتخاب شده', // localized
name: 'delete_redirects',
value: 'delete_redirects',
checked: true
},
{
label: 'حذف زیرصفحههای صفحههای انتخاب شده', // localized
name: 'delete_subpages',
value: 'delete_subpages',
checked: false,
event: Twinkle.batchdelete.callback.toggleSubpages,
subgroup: {
type: 'checkbox',
list: [
{
label: 'حذف صفحههای بحث زیرصفحههای مذکور', // localized
name: 'delete_subpage_talks',
value: 'delete_subpage_talks'
},
{
label: 'حذف تغییرمسیرها به زیرصفحههای مذکور', // localized
name: 'delete_subpage_redirects',
value: 'delete_subpage_redirects'
},
{
label: 'زدودن پیوندها به زیرصفحههای مذکور (فقط در فضای نام اصلی و درگاه)', // localized
name: 'unlink_subpages',
value: 'unlink_subpages'
}
]
}
}
]
}
},
{
label: 'زدودن پیوندها به صفحههای انتخاب شده (فقط در فضای نام اصلی و درگاه)', // localized
name: 'unlink_page',
value: 'unlink',
checked: false
},
{
label: 'حذف کاربردهای پروندههای انتخاب شده (در تمام فضاهای نام)', // localized
name: 'unlink_file',
value: 'unlink_file',
checked: true
}
]
});
form.append({
type: 'input',
name: 'reason',
label: 'دلیل: ', // localized
size: 60
});
var query = {
'action': 'query',
'prop': 'revisions|info|imageinfo',
'inprop': 'protection',
'rvprop': 'size|user'
};
// On categories
if (mw.config.get('wgNamespaceNumber') === 14) {
query.generator = 'categorymembers';
query.gcmtitle = mw.config.get('wgPageName');
query.gcmlimit = Twinkle.getPref('batchMax');
// On Special:PrefixIndex
} else if (mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex') {
query.generator = 'allpages';
query.gaplimit = Twinkle.getPref('batchMax');
if (mw.util.getParamValue('prefix')) {
query.gapnamespace = mw.util.getParamValue('namespace');
query.gapprefix = mw.util.getParamValue('prefix');
} else {
var pathSplit = decodeURIComponent(location.pathname).split('/');
if (pathSplit.length < 3 || pathSplit[2] !== 'Special:PrefixIndex') {
return;
}
var titleSplit = pathSplit[3].split(':');
query.gapnamespace = mw.config.get('wgNamespaceIds')[titleSplit[0].toLowerCase()];
if (titleSplit.length < 2 || typeof query.gapnamespace === 'undefined') {
query.gapnamespace = 0; // article namespace
query.gapprefix = pathSplit.splice(3).join('/');
} else {
pathSplit = pathSplit.splice(4);
pathSplit.splice(0, 0, titleSplit.splice(1).join(':'));
query.gapprefix = pathSplit.join('/');
}
}
// On normal pages
} else {
query.generator = 'links';
query.titles = mw.config.get('wgPageName');
query.gpllimit = Twinkle.getPref('batchMax');
}
var statusdiv = document.createElement('div');
statusdiv.style.padding = '15px'; // just so it doesn't look broken
Window.setContent(statusdiv);
Morebits.status.init(statusdiv);
Window.display();
Twinkle.batchdelete.pages = {};
var statelem = new Morebits.status('گرفتن فهرست صفحهها'); // localized
var wikipedia_api = new Morebits.wiki.api('در حال بارگیری...', query, function(apiobj) { // localized
var xml = apiobj.responseXML;
var $pages = $(xml).find('page').filter(':not([missing])'); // :not([imagerepository="shared"])
$pages.each(function(index, page) {
var $page = $(page);
var ns = $page.attr('ns');
var title = $page.attr('title');
var isRedir = $page.attr('redirect') === '';
var $editprot = $page.find('pr[type="edit"][level="sysop"]');
var isProtected = $editprot.length > 0;
var size = $page.find('rev').attr('size');
var metadata = [];
if (isRedir) {
metadata.push('تغییرمسیر'); // localized
}
if (isProtected) {
metadata.push('محافظت کامل' + // localized
($editprot.attr('expiry') === 'infinity' ? ' بیپایان' : '، انقضا در ' + new Morebits.date($editprot.attr('expiry')).calendar('utc') + ' (UTC)')); // localized
}
if (ns === '6') { // mimic what delimages used to show for files
metadata.push('بارگذاری توسط: ' + $page.find('ii').attr('user')); // localized
metadata.push('آخرین ویرایش توسط: ' + $page.find('rev').attr('user')); // localized
} else {
metadata.push(mw.language.convertNumber(size) + ' بایت'); // localized
}
Twinkle.batchdelete.pages[title] = {
label: title + (metadata.length ? ' (' + metadata.join('؛ ') + ')' : ''), // localized
value: title,
checked: true,
style: isProtected ? 'color:red' : ''
};
});
var form = apiobj.params.form;
form.append({ type: 'header', label: 'صفحهها برای حذف' }); // localized
form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function dBatchSelectAll() {
$(result).find('input[name=pages]:not(:checked)').each(function(_, e) {
e.click(); // check it, and invoke click event so that subgroup can be shown
});
// Check any unchecked subpages too
$('input[name="pages.subpages"]').prop('checked', true);
}
});
form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function dBatchDeselectAll() {
$(result).find('input[name=pages]:checked').each(function(_, e) {
e.click(); // uncheck it, and invoke click event so that subgroup can be hidden
});
}
});
form.append({
type: 'checkbox',
name: 'pages',
id: 'tw-dbatch-pages',
list: $.map(Twinkle.batchdelete.pages, function (e) {
return e;
})
});
form.append({ type: 'submit' });
var result = form.render();
apiobj.params.Window.setContent(result);
var pageCheckboxes = Morebits.quickForm.getElements(result, 'pages') || [];
pageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(pageCheckboxes);
}, statelem);
wikipedia_api.params = { form: form, Window: Window };
wikipedia_api.post();
};
function generateArrowLinks (checkbox) {
var link = Morebits.htmlNode('a', ' >');
link.setAttribute('class', 'tw-dbatch-page-link');
link.setAttribute('href', mw.util.getUrl(checkbox.value));
link.setAttribute('target', '_blank');
checkbox.nextElementSibling.append(link);
}
Twinkle.batchdelete.generateNewPageList = function(form) {
// Update the list of checked pages in Twinkle.batchdelete.pages object
var elements = form.elements.pages;
if (elements instanceof NodeList) { // if there are multiple pages
for (var i = 0; i < elements.length; ++i) {
Twinkle.batchdelete.pages[elements[i].value].checked = elements[i].checked;
}
} else if (elements instanceof HTMLInputElement) { // if there is just one page
Twinkle.batchdelete.pages[elements.value].checked = elements.checked;
}
return new Morebits.quickForm.element({
type: 'checkbox',
name: 'pages',
id: 'tw-dbatch-pages',
list: $.map(Twinkle.batchdelete.pages, function (e) {
return e;
})
}).render();
};
Twinkle.batchdelete.callback.toggleSubpages = function twDbatchToggleSubpages(e) {
var form = e.target.form;
var newPageList, pageCheckboxes, subpageCheckboxes;
if (e.target.checked) {
form.delete_subpage_redirects.checked = form.delete_redirects.checked;
form.delete_subpage_talks.checked = form.delete_talk.checked;
form.unlink_subpages.checked = form.unlink_page.checked;
// If lists of subpages were already loaded once, they are
// available without use of any API calls
if (subpagesLoaded) {
$.each(Twinkle.batchdelete.pages, function(i, el) {
// Get back the subgroup from subgroup_, where we saved it
if (el.subgroup === null && el.subgroup_) {
el.subgroup = el.subgroup_;
}
});
newPageList = Twinkle.batchdelete.generateNewPageList(form);
$('#tw-dbatch-pages').replaceWith(newPageList);
pageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages') || [];
pageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(pageCheckboxes);
subpageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages.subpages') || [];
subpageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(subpageCheckboxes);
return;
}
// Proceed with API calls to get list of subpages
var loadingText = '<strong id="dbatch-subpage-loading">در حال بارگیری... </strong>'; // localized
$(e.target).after(loadingText);
var pages = $(form.pages).map(function(i, el) {
return el.value;
}).get();
var subpageLister = new Morebits.batchOperation();
subpageLister.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
subpageLister.setPageList(pages);
subpageLister.run(function worker (pageName) {
var pageTitle = mw.Title.newFromText(pageName);
// No need to look for subpages in main/file/mediawiki space
if ([0, 6, 8].indexOf(pageTitle.namespace) > -1) {
subpageLister.workerSuccess();
return;
}
var wikipedia_api = new Morebits.wiki.api('گرفتن فهرست زیرصفحههای ' + pageName, { // localized
action: 'query',
prop: 'revisions|info|imageinfo',
generator: 'allpages',
rvprop: 'size',
inprop: 'protection',
gapprefix: pageTitle.title + '/',
gapnamespace: pageTitle.namespace,
gaplimit: 'max', // 500 is max for normal users, 5000 for bots and sysops
pageNameFull: pageName // Not used by API, but added for access in onSuccess()
}, function onSuccess(apiobj) {
var xml = apiobj.responseXML;
var $pages = $(xml).find('page');
var subpageList = [];
$pages.each(function(index, page) {
var $page = $(page);
var ns = $page.attr('ns');
var title = $page.attr('title');
var isRedir = $page.attr('redirect') === '';
var $editprot = $page.find('pr[type="edit"][level="sysop"]');
var isProtected = $editprot.length > 0;
var size = $page.find('rev').attr('size');
var metadata = [];
if (isRedir) {
metadata.push('تغییرمسیر'); // localized
}
if (isProtected) {
metadata.push('محافظت کامل' + // localized
($editprot.attr('expiry') === 'infinity' ? ' بیپایان' : '، انقضا در ' + new Morebits.date($editprot.attr('expiry')).calendar('utc') + ' (UTC)')); // localized
}
if (ns === '6') { // mimic what delimages used to show for files
metadata.push('بارگذاری توسط: ' + $page.find('ii').attr('user')); //localized
metadata.push('آخرین ویرایش توسط: ' + $page.find('rev').attr('user')); // localized
} else {
metadata.push(mw.language.convertNumber(size) + ' بایت'); // localized
}
subpageList.push({
label: title + (metadata.length ? ' (' + metadata.join('؛ ') + ')' : ''), // localized
value: title,
checked: true,
style: isProtected ? 'color:red' : ''
});
});
if (subpageList.length) {
var pageName = apiobj.query.pageNameFull;
Twinkle.batchdelete.pages[pageName].subgroup = {
type: 'checkbox',
name: 'subpages',
className: 'dbatch-subpages',
list: subpageList
};
}
subpageLister.workerSuccess();
}, null /* statusElement */, function onFailure() {
subpageLister.workerFailure();
});
wikipedia_api.post();
}, function postFinish () {
// List 'em on the interface
newPageList = Twinkle.batchdelete.generateNewPageList(form);
$('#tw-dbatch-pages').replaceWith(newPageList);
pageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages') || [];
pageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(pageCheckboxes);
subpageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages.subpages') || [];
subpageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(subpageCheckboxes);
subpagesLoaded = true;
// Remove "Loading... " text
$('#dbatch-subpage-loading').remove();
});
} else if (!e.target.checked) {
$.each(Twinkle.batchdelete.pages, function(i, el) {
if (el.subgroup) {
// Remove subgroup after saving its contents in subgroup_
// so that it can be retrieved easily if user decides to
// delete the subpages again
el.subgroup_ = el.subgroup;
el.subgroup = null;
}
});
newPageList = Twinkle.batchdelete.generateNewPageList(form);
$('#tw-dbatch-pages').replaceWith(newPageList);
pageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages') || [];
pageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(pageCheckboxes);
}
};
Twinkle.batchdelete.callback.evaluate = function twinklebatchdeleteCallbackEvaluate(event) {
Morebits.wiki.actionCompleted.notice = 'حذف دستهجمعی کامل شد'; // localized
var form = event.target;
var numProtected = $(Morebits.quickForm.getElements(form, 'pages')).filter(function(index, element) {
return element.checked && element.nextElementSibling.style.color === 'red';
}).length;
if (numProtected > 0 && !confirm('شما در حال حذف ' + mw.language.convertNumber(numProtected) + ' صفحهٔ کاملاً محافظتشده هستید. آیا مطمئن هستید؟')) { // localized
return;
}
var pages = form.getChecked('pages');
var subpages = form.getChecked('pages.subpages');
var reason = form.reason.value;
var delete_page = form.delete_page.checked;
var delete_talk, delete_redirects, delete_subpages;
var delete_subpage_redirects, delete_subpage_talks, unlink_subpages;
if (delete_page) {
delete_talk = form.delete_talk.checked;
delete_redirects = form.delete_redirects.checked;
delete_subpages = form.delete_subpages.checked;
if (delete_subpages) {
delete_subpage_redirects = form.delete_subpage_redirects.checked;
delete_subpage_talks = form.delete_subpage_talks.checked;
unlink_subpages = form.unlink_subpages.checked;
}
}
var unlink_page = form.unlink_page.checked;
var unlink_file = form.unlink_file.checked;
if (!reason) {
alert('شما باید دلیلی ارائه کنید!'); // localized
return;
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
if (!pages) {
Morebits.status.error('Error', 'چیزی برای حذف وجود نداشت؛ حذف متوقف شد'); // localized
return;
}
var pageDeleter = new Morebits.batchOperation(delete_page ? 'حذف صفحهها' : 'شروع وظایف درخواستشده'); // localized
pageDeleter.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
// we only need the initial status lines if we're deleting the pages in the pages array
pageDeleter.setOption('preserveIndividualStatusLines', delete_page);
pageDeleter.setPageList(pages);
pageDeleter.run(function worker(pageName) {
var params = {
page: pageName,
delete_page: delete_page,
delete_talk: delete_talk,
delete_redirects: delete_redirects,
unlink_page: unlink_page,
unlink_file: unlink_file && /^(File|Image|پرونده|تصویر):/i.test(pageName), // localized
reason: reason,
pageDeleter: pageDeleter
};
var wikipedia_page = new Morebits.wiki.page(pageName, 'حذف صفحهٔ ' + pageName); // localized
wikipedia_page.setCallbackParameters(params);
if (delete_page) {
wikipedia_page.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
wikipedia_page.suppressProtectWarning();
wikipedia_page.deletePage(Twinkle.batchdelete.callbacks.doExtras, pageDeleter.workerFailure);
} else {
Twinkle.batchdelete.callbacks.doExtras(wikipedia_page);
}
}, function postFinish() {
if (delete_subpages) {
var subpageDeleter = new Morebits.batchOperation('حذف زیرصفحهها'); // localized
subpageDeleter.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
subpageDeleter.setOption('preserveIndividualStatusLines', true);
subpageDeleter.setPageList(subpages);
subpageDeleter.run(function(pageName) {
var params = {
page: pageName,
delete_page: true,
delete_talk: delete_subpage_talks,
delete_redirects: delete_subpage_redirects,
unlink_page: unlink_subpages,
unlink_file: false,
reason: reason,
pageDeleter: subpageDeleter
};
var wikipedia_page = new Morebits.wiki.page(pageName, 'حذف زیرصفحه با عنوان ' + pageName); // localized
wikipedia_page.setCallbackParameters(params);
wikipedia_page.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
wikipedia_page.suppressProtectWarning();
wikipedia_page.deletePage(Twinkle.batchdelete.callbacks.doExtras, pageDeleter.workerFailure);
});
}
});
};
Twinkle.batchdelete.callbacks = {
// this stupid parameter name is a temporary thing until I implement an overhaul
// of Morebits.wiki.* callback parameters
doExtras: function(thingWithParameters) {
var params = thingWithParameters.parent ? thingWithParameters.parent.getCallbackParameters() :
thingWithParameters.getCallbackParameters();
// the initial batch operation's job is to delete the page, and that has
// succeeded by now
params.pageDeleter.workerSuccess(thingWithParameters);
var query, wikipedia_api;
if (params.unlink_page) {
Twinkle.batchdelete.unlinkCache = {};
query = {
'action': 'query',
'list': 'backlinks',
'blfilterredir': 'nonredirects',
'blnamespace': [0, 100], // main space and portal space only
'bltitle': params.page,
'bllimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
wikipedia_api = new Morebits.wiki.api('گرفتن پیوندهای ورودی', query, Twinkle.batchdelete.callbacks.unlinkBacklinksMain); // localized
wikipedia_api.params = params;
wikipedia_api.post();
}
if (params.unlink_file) {
query = {
'action': 'query',
'list': 'imageusage',
'iutitle': params.page,
'iulimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
wikipedia_api = new Morebits.wiki.api('گرفتن پیوندها به پرونده', query, Twinkle.batchdelete.callbacks.unlinkImageInstancesMain); // localized
wikipedia_api.params = params;
wikipedia_api.post();
}
if (params.delete_page) {
if (params.delete_redirects) {
query = {
'action': 'query',
'titles': params.page,
'prop': 'redirects',
'rdlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
wikipedia_api = new Morebits.wiki.api('گرفتن تغییرمسیرها', query, Twinkle.batchdelete.callbacks.deleteRedirectsMain); // localized
wikipedia_api.params = params;
wikipedia_api.post();
}
if (params.delete_talk) {
var pageTitle = mw.Title.newFromText(params.page);
if (pageTitle && pageTitle.namespace % 2 === 0 && pageTitle.namespace !== 2) {
pageTitle.namespace++; // now pageTitle is the talk page title!
query = {
'action': 'query',
'titles': pageTitle.toText()
};
wikipedia_api = new Morebits.wiki.api('بررسی این که صفحهٔ بحثی وجود دارد یانه', query, Twinkle.batchdelete.callbacks.deleteTalk); // localized
wikipedia_api.params = params;
wikipedia_api.params.talkPage = pageTitle.toText();
wikipedia_api.post();
}
}
}
},
deleteRedirectsMain: function(apiobj) {
var xml = apiobj.responseXML;
var pages = $(xml).find('rd').map(function() {
return $(this).attr('title');
}).get();
if (!pages.length) {
return;
}
var redirectDeleter = new Morebits.batchOperation('حذف تغییرمسیرها به ' + apiobj.params.page); // localized
redirectDeleter.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
redirectDeleter.setPageList(pages);
redirectDeleter.run(function(pageName) {
var wikipedia_page = new Morebits.wiki.page(pageName, 'حذف ' + pageName); // localized
wikipedia_page.setEditSummary('[[وو:محس#ع۸|ع۸]]: تغییرمسیر به صفحهٔ حذف شده «' + apiobj.params.page + '»' + Twinkle.getPref('deletionSummaryAd')); // localized
wikipedia_page.deletePage(redirectDeleter.workerSuccess, redirectDeleter.workerFailure);
});
},
deleteTalk: function(apiobj) {
var xml = apiobj.responseXML;
var exists = $(xml).find('page:not([missing])').length > 0;
if (!exists) {
// no talk page; forget about it
return;
}
var page = new Morebits.wiki.page(apiobj.params.talkPage, 'حذف صفحهٔ بحث ' + apiobj.params.page); // localized
page.setEditSummary('[[وو:محس#ع۸|ع۸]]: [[راهنما: صفحه بحث|صفحهٔ بحث]] صفحهٔ حذف شده «' + apiobj.params.page + '»' + Twinkle.getPref('deletionSummaryAd')); // localized
page.deletePage();
},
unlinkBacklinksMain: function(apiobj) {
var xml = apiobj.responseXML;
var pages = $(xml).find('bl').map(function() {
return $(this).attr('title');
}).get();
if (!pages.length) {
return;
}
var unlinker = new Morebits.batchOperation('زدودن پیوندهای ورودی به ' + apiobj.params.page); // localized
unlinker.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
unlinker.setPageList(pages);
unlinker.run(function(pageName) {
var wikipedia_page = new Morebits.wiki.page(pageName, 'زدودن پیوند از ' + pageName); // localized
var params = $.extend({}, apiobj.params);
params.title = pageName;
params.unlinker = unlinker;
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkBacklinks);
});
},
unlinkBacklinks: function(pageobj) {
var params = pageobj.getCallbackParameters();
if (!pageobj.exists()) {
// we probably just deleted it, as a recursive backlink
params.unlinker.workerSuccess(pageobj);
return;
}
var text;
if (params.title in Twinkle.batchdelete.unlinkCache) {
text = Twinkle.batchdelete.unlinkCache[params.title];
} else {
text = pageobj.getPageText();
}
var old_text = text;
var wikiPage = new Morebits.wikitext.page(text);
wikiPage.removeLink(params.page);
text = wikiPage.getText();
Twinkle.batchdelete.unlinkCache[params.title] = text;
if (text === old_text) {
// Nothing to do, return
params.unlinker.workerSuccess(pageobj);
return;
}
pageobj.setEditSummary('زدودن پیوندها به صفحهٔ حذف شده ' + params.page + Twinkle.getPref('deletionSummaryAd')); // localized
pageobj.setPageText(text);
pageobj.setCreateOption('nocreate');
pageobj.setMaxConflictRetries(10);
pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
},
unlinkImageInstancesMain: function(apiobj) {
var xml = apiobj.responseXML;
var pages = $(xml).find('iu').map(function() {
return $(this).attr('title');
}).get();
if (!pages.length) {
return;
}
var unlinker = new Morebits.batchOperation('حذف پیوندهای ورودی به ' + apiobj.params.page); // localized
unlinker.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
unlinker.setPageList(pages);
unlinker.run(function(pageName) {
var wikipedia_page = new Morebits.wiki.page(pageName, 'حذف کاربردهای پرونده در ' + pageName); // localized
var params = $.extend({}, apiobj.params);
params.title = pageName;
params.unlinker = unlinker;
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkImageInstances);
});
},
unlinkImageInstances: function(pageobj) {
var params = pageobj.getCallbackParameters();
if (!pageobj.exists()) {
// we probably just deleted it, as a recursive backlink
params.unlinker.workerSuccess(pageobj);
return;
}
var image = params.page.replace(/^(?:Image|File|تصویر|پرونده):/, ''); // localized
var text;
if (params.title in Twinkle.batchdelete.unlinkCache) {
text = Twinkle.batchdelete.unlinkCache[params.title];
} else {
text = pageobj.getPageText();
}
var old_text = text;
var wikiPage = new Morebits.wikitext.page(text);
wikiPage.commentOutImage(image, 'کاربرد تصویر حذف شده پنهان شد'); // localized
text = wikiPage.getText();
Twinkle.batchdelete.unlinkCache[params.title] = text;
if (text === old_text) {
pageobj.getStatusElement().error('حذف کاربرد ' + image + ' از ' + pageobj.getPageName() + ' ناموفق بود'); // localized
params.unlinker.workerFailure(pageobj);
return;
}
pageobj.setEditSummary('حذف کاربردهای ' + image + ' که حذف شدهاست؛ دلیل حذف: ' + params.reason + Twinkle.getPref('deletionSummaryAd')); // localized
pageobj.setPageText(text);
pageobj.setCreateOption('nocreate');
pageobj.setMaxConflictRetries(10);
pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
}
};
})(jQuery);
// </nowiki>
/* ===== twinklebatchprotect.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklebatchprotect.js: Batch protect module (sysops only)
****************************************
* Mode of invocation: Tab ("P-batch")
* Active on: Existing project pages and user pages; existing and
* non-existing categories; Special:PrefixIndex
*/
Twinkle.batchprotect = function twinklebatchprotect() {
if (Morebits.userIsSysop && ((mw.config.get('wgArticleId') > 0 && (mw.config.get('wgNamespaceNumber') === 2 ||
mw.config.get('wgNamespaceNumber') === 4)) || mw.config.get('wgNamespaceNumber') === 14 ||
mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex')) {
Twinkle.addPortletLink(Twinkle.batchprotect.callback, 'محافظت دستهجمعی', 'tw-pbatch', 'محافظت صفحههایی که از این صفحه پیوند دارند'); // localized
}
};
Twinkle.batchprotect.unlinkCache = {};
Twinkle.batchprotect.callback = function twinklebatchprotectCallback() {
var Window = new Morebits.simpleWindow(600, 400);
Window.setTitle('محافظت دستهجمعی'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('سیاست حفاظت از صفحهها', 'وو:حفاظت'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#protect'); // localized
var form = new Morebits.quickForm(Twinkle.batchprotect.callback.evaluate);
form.append({
type: 'checkbox',
name: 'editmodify',
event: Twinkle.protect.formevents.editmodify,
list: [
{
label: 'تغییر حفاظت در برابر ویرایش', // localized
value: 'editmodify',
tooltip: 'فقط برای صفحههای از پیش موجود.', // localized
checked: true
}
]
});
var editlevel = form.append({
type: 'select',
name: 'editlevel',
label: 'حفاظت در برابر ویرایش:', // localized
event: Twinkle.protect.formevents.editlevel
});
editlevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
editlevel.append({
type: 'option',
label: 'تأییدشدهٔ خودکار', // localized
value: 'autoconfirmed'
});
editlevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed'
});
editlevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
editlevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
form.append({
type: 'select',
name: 'editexpiry',
label: 'زمان سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', selected: true, value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'سفارشی...', value: 'custom' } // localized
]
});
form.append({
type: 'checkbox',
name: 'movemodify',
event: Twinkle.protect.formevents.movemodify,
list: [
{
label: 'تغییر حفاظت در برابر انتقال', // localized
value: 'movemodify',
tooltip: 'فقط برای صفحههای از پیش موجود.', // localized
checked: true
}
]
});
var movelevel = form.append({
type: 'select',
name: 'movelevel',
label: 'حفاظت در برابر انتقال:', // localized
event: Twinkle.protect.formevents.movelevel
});
movelevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
movelevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed'
});
movelevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
movelevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
form.append({
type: 'select',
name: 'moveexpiry',
label: 'زمان سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', selected: true, value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'سفارشی...', value: 'custom' } // localized
]
});
form.append({
type: 'checkbox',
name: 'createmodify',
event: function twinklebatchprotectFormCreatemodifyEvent(e) {
e.target.form.createlevel.disabled = !e.target.checked;
e.target.form.createexpiry.disabled = !e.target.checked || (e.target.form.createlevel.value === 'all');
e.target.form.createlevel.style.color = e.target.form.createexpiry.style.color = e.target.checked ? '' : 'transparent';
},
list: [
{
label: 'تغییر حفاظت در برابر ایجاد', // localized
value: 'createmodify',
tooltip: 'فقط برای صفحههایی که موجود نیستند.', // localized
checked: true
}
]
});
var createlevel = form.append({
type: 'select',
name: 'createlevel',
label: 'حفاظت در برابر ایجاد:', // localized
event: Twinkle.protect.formevents.createlevel
});
createlevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
createlevel.append({
type: 'option',
label: 'تأییدشده خودکار', // localized
value: 'autoconfirmed'
});
createlevel.append({
type: 'option',
label: 'تأییدشده پایدار', // localized
value: 'extendedconfirmed'
});
createlevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
createlevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
form.append({
type: 'select',
name: 'createexpiry',
label: 'زمان سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', selected: true, value: 'indefinite' }, // localized
{ label: 'سفارشی...', value: 'custom' } // localized
]
});
form.append({
type: 'header',
label: '' // horizontal rule
});
form.append({
type: 'input',
name: 'reason',
label: 'دلیل: ', // localized
size: 60,
tooltip: 'برای سیاههٔ حفاظت و تاریخچهٔ صفحه.' // localized
});
var query = {
'action': 'query',
'prop': 'revisions|info',
'rvprop': 'size',
'inprop': 'protection'
};
if (mw.config.get('wgNamespaceNumber') === 14) { // categories
query.generator = 'categorymembers';
query.gcmtitle = mw.config.get('wgPageName');
query.gcmlimit = Twinkle.getPref('batchMax');
} else if (mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex') {
query.generator = 'allpages';
query.gapnamespace = mw.util.getParamValue('namespace') || $('select[name=namespace]').val();
query.gapprefix = mw.util.getParamValue('prefix') || $('input[name=prefix]').val();
query.gaplimit = Twinkle.getPref('batchMax');
} else {
query.generator = 'links';
query.titles = mw.config.get('wgPageName');
query.gpllimit = Twinkle.getPref('batchMax');
}
var statusdiv = document.createElement('div');
statusdiv.style.padding = '15px'; // just so it doesn't look broken
Window.setContent(statusdiv);
Morebits.status.init(statusdiv);
Window.display();
var statelem = new Morebits.status('گرفتن فهرست صفحهها'); // localized
var wikipedia_api = new Morebits.wiki.api('در حال بارگیری...', query, function(apiobj) { // localized
var xml = apiobj.responseXML;
var $pages = $(xml).find('page');
var list = [];
$pages.each(function(index, page) {
var $page = $(page);
var title = $page.attr('title');
var isRedir = $page.attr('redirect') === ''; // XXX ??
var missing = $page.attr('missing') === ''; // XXX ??
var size = $page.find('rev').attr('size');
var $editProt;
var metadata = [];
if (missing) {
metadata.push('صفحهٔ ناموجود'); // localized
$editProt = $page.find('pr[type="create"][level="sysop"]');
} else {
if (isRedir) {
metadata.push('تغییرمسیر'); // localized
}
metadata.push(mw.language.convertNumber(size) + ' بایت'); // localized
$editProt = $page.find('pr[type="edit"][level="sysop"]');
}
if ($editProt.length > 0) {
metadata.push('محافظت کامل' + (missing ? ' در برابر ایجاد' : '') + // localized
($editProt.attr('expiry') === 'infinity' ? ' بیپایان' : '، سرآمدن در ' + new Morebits.date($editProt.attr('expiry')).calendar('utc') + ' (UTC)')); // localized
}
list.push({ label: title + (metadata.length ? ' (' + metadata.join('; ') + ')' : ''), value: title, checked: true, style: $editProt.length > 0 ? 'color:red' : '' });
});
form.append({ type: 'header', label: 'صفحههایی که محافظت خواهند شد' }); // localized
form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', true);
}
});
form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', false);
}
});
form.append({
type: 'checkbox',
name: 'pages',
list: list
});
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Morebits.checkboxShiftClickSupport(Morebits.quickForm.getElements(result, 'pages'));
}, statelem);
wikipedia_api.post();
};
Twinkle.batchprotect.currentProtectCounter = 0;
Twinkle.batchprotect.currentprotector = 0;
Twinkle.batchprotect.callback.evaluate = function twinklebatchprotectCallbackEvaluate(event) {
Morebits.wiki.actionCompleted.notice = 'محافظت دستهجمعی کامل شد'; // localized
var form = event.target;
var numProtected = $(Morebits.quickForm.getElements(form, 'pages')).filter(function(index, element) {
return element.checked && element.nextElementSibling.style.color === 'red';
}).length;
if (numProtected > 0 && !confirm('شما در حال اعمال تغییرات بر روی' + mw.language.convertNumber(numProtected) + ' صفحهٔ کاملاً حفاظتشده هستید. آیا مطمئن هستید؟')) { // localized
return;
}
var pages = form.getChecked('pages');
var reason = form.reason.value;
var editmodify = form.editmodify.checked;
var editlevel = form.editlevel.value;
var editexpiry = form.editexpiry.value;
var movemodify = form.movemodify.checked;
var movelevel = form.movelevel.value;
var moveexpiry = form.moveexpiry.value;
var createmodify = form.createmodify.checked;
var createlevel = form.createlevel.value;
var createexpiry = form.createexpiry.value;
if (!reason) {
alert("باید دلیلی برای کارتان ارائه کنید!"); // localized
return;
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
if (!pages) {
Morebits.status.error('Error', 'چیزی برای حفاظت موجود نیست؛ در حال لغو کردن'); // localized
return;
}
var batchOperation = new Morebits.batchOperation('اعمال تنظیمات محافظت'); // localized
batchOperation.setOption('chunkSize', Twinkle.getPref('batchProtectChunks'));
batchOperation.setOption('preserveIndividualStatusLines', true);
batchOperation.setPageList(pages);
batchOperation.run(function(pageName) {
var query = {
'action': 'query',
'titles': pageName
};
var wikipedia_api = new Morebits.wiki.api('بررسی موجود بودن صفحه' + pageName + '', query, // localized
Twinkle.batchprotect.callbacks.main, null, batchOperation.workerFailure);
wikipedia_api.params = {
page: pageName,
reason: reason,
editmodify: editmodify,
editlevel: editlevel,
editexpiry: editexpiry,
movemodify: movemodify,
movelevel: movelevel,
moveexpiry: moveexpiry,
createmodify: createmodify,
createlevel: createlevel,
createexpiry: createexpiry,
batchOperation: batchOperation
};
wikipedia_api.post();
});
};
Twinkle.batchprotect.callbacks = {
main: function(apiobj) {
var xml = apiobj.responseXML;
var normal = $(xml).find('normalized n').attr('to');
if (normal) {
apiobj.params.page = normal;
}
var exists = $(xml).find('page').attr('missing') !== '';
var page = new Morebits.wiki.page(apiobj.params.page, 'محافظت ' + apiobj.params.page); // localized
var takenAction = false;
if (exists && apiobj.params.editmodify) {
page.setEditProtection(apiobj.params.editlevel, apiobj.params.editexpiry);
takenAction = true;
}
if (exists && apiobj.params.movemodify) {
page.setMoveProtection(apiobj.params.movelevel, apiobj.params.moveexpiry);
takenAction = true;
}
if (!exists && apiobj.params.createmodify) {
page.setCreateProtection(apiobj.params.createlevel, apiobj.params.createexpiry);
takenAction = true;
}
if (!takenAction) {
Morebits.status.warn('محافظت صفحهٔ ' + apiobj.params.page, 'از پیش وجود ' + (exists ? 'دارد' : 'ندارد') + '؛ چیزی برای انجام دادن باقی نماندهاست'); // localizing
apiobj.params.batchOperation.workerFailure(apiobj);
return;
}
page.setEditSummary(apiobj.params.reason);
page.protect(apiobj.params.batchOperation.workerSuccess, apiobj.params.batchOperation.workerFailure);
}
};
})(jQuery);
// </nowiki>
/* ===== twinklebatchundelete.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklebatchundelete.js: Batch undelete module
****************************************
* Mode of invocation: Tab ("Und-batch")
* Active on: Existing user and project pages
*/
Twinkle.batchundelete = function twinklebatchundelete() {
if (!Morebits.userIsSysop || !mw.config.get('wgArticleId') || (
mw.config.get('wgNamespaceNumber') !== mw.config.get('wgNamespaceIds').user &&
mw.config.get('wgNamespaceNumber') !== mw.config.get('wgNamespaceIds').project)) {
return;
}
Twinkle.addPortletLink(Twinkle.batchundelete.callback, 'احیای دستهجمعی', 'tw-batch-undel', "احیای چندین صفحه به طور همزمان"); // localized
};
Twinkle.batchundelete.callback = function twinklebatchundeleteCallback() {
var Window = new Morebits.simpleWindow(600, 400);
Window.setScriptName('توینکل'); // localized
Window.setTitle('احیای دستهجمعی'); // localizde
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#batchundelete'); // localized
var form = new Morebits.quickForm(Twinkle.batchundelete.callback.evaluate);
form.append({
type: 'checkbox',
list: [
{
label: 'صفحههای بحث مرتبط با صفحههای احیا شده را در صورتی که وجود داشتهاند، احیا کن', // localized
name: 'undel_talk',
value: 'undel_talk',
checked: true
}
]
});
form.append({
type: 'input',
name: 'reason',
label: 'دلیل: ', // localized
size: 60
});
var statusdiv = document.createElement('div');
statusdiv.style.padding = '15px'; // just so it doesn't look broken
Window.setContent(statusdiv);
Morebits.status.init(statusdiv);
Window.display();
var query = {
'action': 'query',
'generator': 'links',
'prop': 'info',
'inprop': 'protection',
'titles': mw.config.get('wgPageName'),
'gpllimit': Twinkle.getPref('batchMax')
};
var statelem = new Morebits.status('تهیهٔ فهرست صفحهها'); // localized
var wikipedia_api = new Morebits.wiki.api('در حال بارگیری...', query, function(apiobj) { // localized
var xml = apiobj.responseXML;
var $pages = $(xml).find('page[missing]');
var list = [];
$pages.each(function(index, page) {
var $page = $(page);
var title = $page.attr('title');
var $editprot = $page.find('pr[type="create"][level="sysop"]');
var isProtected = $editprot.length > 0;
list.push({
label: title + (isProtected ? ' (محافظتشده در برابر ایجاد' +
($editprot.attr('expiry') === 'infinity' ? ' به صورت بیپایان' : '، انقضا در ' + new Morebits.date($editprot.attr('expiry')).calendar('utc') + ' (UTC)') + ')' : ''), // localized
value: title,
checked: true,
style: isProtected ? 'color:red' : ''
});
});
apiobj.params.form.append({ type: 'header', label: 'صفحهها برای احیا' }); // localized
apiobj.params.form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', true);
}
});
apiobj.params.form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', false);
}
});
apiobj.params.form.append({
type: 'checkbox',
name: 'pages',
list: list
});
apiobj.params.form.append({ type: 'submit' });
var result = apiobj.params.form.render();
apiobj.params.Window.setContent(result);
Morebits.checkboxShiftClickSupport(Morebits.quickForm.getElements(result, 'pages'));
}, statelem);
wikipedia_api.params = { form: form, Window: Window };
wikipedia_api.post();
};
Twinkle.batchundelete.callback.evaluate = function(event) {
Morebits.wiki.actionCompleted.notice = 'احیای دستهجمعی کامل شد'; // localized
var numProtected = $(Morebits.quickForm.getElements(event.target, 'pages')).filter(function(index, element) {
return element.checked && element.nextElementSibling.style.color === 'red';
}).length;
if (numProtected > 0 && !confirm('شما در حال احیای ' + numProtected + ' صفحه هستید که در برابر ایجاد محافظت شدهاند. آیا مطمئن هستید؟')) { // localized
return;
}
var pages = event.target.getChecked('pages');
var reason = event.target.reason.value;
var undel_talk = event.target.reason.value;
if (!reason) {
alert('باید دلیلی ارائه کنید!'); // localized
return;
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(event.target);
if (!pages) {
Morebits.status.error('Error', 'چیزی برای احیا موجود نیست، احیا متوقف شد'); // localized
return;
}
var pageUndeleter = new Morebits.batchOperation('Undeleting pages');
pageUndeleter.setOption('chunkSize', Twinkle.getPref('batchUndeleteChunks'));
pageUndeleter.setOption('preserveIndividualStatusLines', true);
pageUndeleter.setPageList(pages);
pageUndeleter.run(function(pageName) {
var params = {
page: pageName,
undel_talk: undel_talk,
reason: reason,
pageUndeleter: pageUndeleter
};
var wikipedia_page = new Morebits.wiki.page(pageName, 'احیای صفحهٔ ' + pageName); // localized
wikipedia_page.setCallbackParameters(params);
wikipedia_page.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
wikipedia_page.suppressProtectWarning();
wikipedia_page.setMaxRetries(3); // temporary increase from 2 to make batchundelete more likely to succeed [[phab:T222402]] #613
wikipedia_page.undeletePage(Twinkle.batchundelete.callbacks.doExtras, pageUndeleter.workerFailure);
});
};
Twinkle.batchundelete.callbacks = {
// this stupid parameter name is a temporary thing until I implement an overhaul
// of Morebits.wiki.* callback parameters
doExtras: function(thingWithParameters) {
var params = thingWithParameters.parent ? thingWithParameters.parent.getCallbackParameters() :
thingWithParameters.getCallbackParameters();
// the initial batch operation's job is to delete the page, and that has
// succeeded by now
params.pageUndeleter.workerSuccess(thingWithParameters);
var query, wikipedia_api;
if (params.undel_talk) {
var talkpagename = new mw.Title(params.page).getTalkPage().getPrefixedText();
if (talkpagename !== params.page) {
query = {
'action': 'query',
'prop': 'deletedrevisions',
'drvprop': 'ids',
'drvlimit': 1,
'titles': talkpagename
};
wikipedia_api = new Morebits.wiki.api('بررسی صفحهٔ بحث برای نسخههای حذفشده', query, Twinkle.batchundelete.callbacks.undeleteTalk); // localized
wikipedia_api.params = params;
wikipedia_api.params.talkPage = talkpagename;
wikipedia_api.post();
}
}
},
undeleteTalk: function(apiobj) {
var xml = apiobj.responseXML;
var exists = $(xml).find('page:not([missing])').length > 0;
var delrevs = $(xml).find('rev').attr('revid');
if (exists || !delrevs) {
// page exists or has no deleted revisions; forget about it
return;
}
var page = new Morebits.wiki.page(apiobj.params.talkPage, 'احیای صفحهٔ بحث ' + apiobj.params.page); // localized
page.setEditSummary('احیای [[راهنما:صفحه بحث|صفحهٔ بحث]] "' + apiobj.params.page + '"' + Twinkle.getPref('deletionSummaryAd')); // localized
page.undeletePage();
}
};
})(jQuery);
// </nowiki>
// </nowiki>
f552q4r4ep7iyc8juk9exmfzfym8dmi
908989
908987
2026-07-30T16:01:52Z
Mamouri
34239
بهروزرسانی بستهٔ آزمایشی توینکل
908989
javascript
text/javascript
// <nowiki>
// توینکل برای ویکیواژهٔ فارسی — بستهٔ آزمایشی (کاربر:Mamouri)
/* ===== morebits.js ===== */
// <nowiki>
/**
* morebits.js
* ===========
* A library full of lots of goodness for user scripts on MediaWiki wikis, including Wikipedia.
*
* The highlights include:
* - Morebits.quickForm class - generates quick HTML forms on the fly
* - Morebits.wiki.api class - makes calls to the MediaWiki API
* - Morebits.wiki.page class - modifies pages on the wiki (edit, revert, delete, etc.)
* - Morebits.wikitext class - contains some utilities for dealing with wikitext
* - Morebits.status class - a rough-and-ready status message displayer, used by the Morebits.wiki classes
* - Morebits.simpleWindow class - a wrapper for jQuery UI Dialog with a custom look and extra features
*
* Dependencies:
* - The whole thing relies on jQuery. But most wikis should provide this by default.
* - Morebits.quickForm, Morebits.simpleWindow, and Morebits.status rely on the "morebits.css" file for their styling.
* - Morebits.simpleWindow relies on jquery UI Dialog (from ResourceLoader module name 'jquery.ui').
* - Morebits.quickForm tooltips rely on Tipsy (ResourceLoader module name 'jquery.tipsy').
* For external installations, Tipsy is available at [http://onehackoranother.com/projects/jquery/tipsy].
* - To create a gadget based on morebits.js, use this syntax in MediaWiki:Gadgets-definition:
* * GadgetName[ResourceLoader|dependencies=mediawiki.user,mediawiki.util,jquery.ui,jquery.tipsy]|morebits.js|morebits.css|GadgetName.js
* - Alternatively, you can configure morebits.js as a hidden gadget in MediaWiki:Gadgets-definition:
* * morebits[ResourceLoader|dependencies=mediawiki.user,mediawiki.util,jquery.ui,jquery.tipsy|hidden]|morebits.js|morebits.css
* and then load ext.gadget.morebits as one of the dependencies for the new gadget
*
* All the stuff here works on all browsers for which MediaWiki provides JavaScript support.
*
* This library is maintained by the maintainers of Twinkle.
* For queries, suggestions, help, etc., head to [[Wikipedia talk:Twinkle]] on English Wikipedia [http://en.wikipedia.org].
* The latest development source is available at [https://github.com/azatoth/twinkle/blob/master/morebits.js].
*/
(function (window, document, $) { // Wrap entire file with anonymous function
var Morebits = {};
window.Morebits = Morebits; // allow global access
/**
* **************** Morebits.userIsInGroup() ****************
* Simple helper function to see what groups a user might belong
* @param {string} group eg. `sysop`, `extendedconfirmed`, etc
* @returns {boolean}
*/
Morebits.userIsInGroup = function (group) {
return mw.config.get('wgUserGroups').indexOf(group) !== -1;
};
// Used a lot
Morebits.userIsSysop = Morebits.userIsInGroup('sysop');
/**
* **************** Morebits.sanitizeIPv6() ****************
* JavaScript translation of the MediaWiki core function IP::sanitizeIP() in
* includes/utils/IP.php.
* Converts an IPv6 address to the canonical form stored and used by MediaWiki.
* @param {string} address - The IPv6 address
* @returns {string}
*/
Morebits.sanitizeIPv6 = function (address) {
address = address.trim();
if (address === '') {
return null;
}
if (!mw.util.isIPv6Address(address)) {
return address; // nothing else to do for IPv4 addresses or invalid ones
}
// Remove any whitespaces, convert to upper case
address = address.toUpperCase();
// Expand zero abbreviations
var abbrevPos = address.indexOf('::');
if (abbrevPos > -1) {
// We know this is valid IPv6. Find the last index of the
// address before any CIDR number (e.g. "a:b:c::/24").
var CIDRStart = address.indexOf('/');
var addressEnd = CIDRStart > -1 ? CIDRStart - 1 : address.length - 1;
// If the '::' is at the beginning...
var repeat, extra, pad;
if (abbrevPos === 0) {
repeat = '0:';
extra = address === '::' ? '0' : ''; // for the address '::'
pad = 9; // 7+2 (due to '::')
// If the '::' is at the end...
} else if (abbrevPos === (addressEnd - 1)) {
repeat = ':0';
extra = '';
pad = 9; // 7+2 (due to '::')
// If the '::' is in the middle...
} else {
repeat = ':0';
extra = ':';
pad = 8; // 6+2 (due to '::')
}
var replacement = repeat;
pad -= address.split(':').length - 1;
for (var i = 1; i < pad; i++) {
replacement += repeat;
}
replacement += extra;
address = address.replace('::', replacement);
}
// Remove leading zeros from each bloc as needed
address = address.replace(/(^|:)0+([0-9A-Fa-f]{1,4})/g, '$1$2');
return address;
};
/**
* **************** Morebits.quickForm ****************
* Morebits.quickForm is a class for creation of simple and standard forms without much
* specific coding.
*
* Index to Morebits.quickForm element types:
*
* select A combo box (aka drop-down).
* - Attributes: name, label, multiple, size, list, event, disabled
* option An element for a combo box.
* - Attributes: value, label, selected, disabled
* optgroup A group of "option"s.
* - Attributes: label, list
* field A fieldset (aka group box).
* - Attributes: name, label, disabled
* checkbox A checkbox. Must use "list" parameter.
* - Attributes: name, list, event
* - Attributes (within list): name, label, value, checked, disabled, event, subgroup
* radio A radio button. Must use "list" parameter.
* - Attributes: name, list, event
* - Attributes (within list): name, label, value, checked, disabled, event, subgroup
* input A text box.
* - Attributes: name, label, value, size, disabled, required, readonly, maxlength, event
* dyninput A set of text boxes with "Remove" buttons and an "Add" button.
* - Attributes: name, label, min, max, sublabel, value, size, maxlength, event
* hidden An invisible form field.
* - Attributes: name, value
* header A level 5 header.
* - Attributes: label
* div A generic placeholder element or label.
* - Attributes: name, label
* submit A submit button. Morebits.simpleWindow moves these to the footer of the dialog.
* - Attributes: name, label, disabled
* button A generic button.
* - Attributes: name, label, disabled, event
* textarea A big, multi-line text box.
* - Attributes: name, label, value, cols, rows, disabled, required, readonly
* fragment A DocumentFragment object.
* - No attributes, and no global attributes except adminonly
*
* Global attributes: id, className, style, tooltip, extra, adminonly
*/
/**
* @constructor
* @param {event} event - Function to execute when form is submitted
* @param {string} [eventType=submit] - Type of the event (default: submit)
*/
Morebits.quickForm = function QuickForm(event, eventType) {
this.root = new Morebits.quickForm.element({ type: 'form', event: event, eventType: eventType });
};
/**
* Renders the HTML output of the quickForm
* @returns {HTMLElement}
*/
Morebits.quickForm.prototype.render = function QuickFormRender() {
var ret = this.root.render();
ret.names = {};
return ret;
};
/**
* Append element to the form
* @param {(Object|Morebits.quickForm.element)} data - a quickform element, or the object with which
* a quickform element is constructed.
* @returns {Morebits.quickForm.element} - same as what is passed to the function
*/
Morebits.quickForm.prototype.append = function QuickFormAppend(data) {
return this.root.append(data);
};
/**
* @constructor
* @param {Object} data - Object representing the quickform element. See class documentation
* comment for available types and attributes for each.
*/
Morebits.quickForm.element = function QuickFormElement(data) {
this.data = data;
this.childs = [];
this.id = Morebits.quickForm.element.id++;
};
Morebits.quickForm.element.id = 0;
/**
* Appends an element to current element
* @param {Morebits.quickForm.element} data A quickForm element or the object required to
* create the quickForm element
* @returns {Morebits.quickForm.element} The same element passed in
*/
Morebits.quickForm.element.prototype.append = function QuickFormElementAppend(data) {
var child;
if (data instanceof Morebits.quickForm.element) {
child = data;
} else {
child = new Morebits.quickForm.element(data);
}
this.childs.push(child);
return child;
};
/**
* Renders the HTML output for the quickForm element
* This should be called without parameters: form.render()
* @returns {HTMLElement}
*/
Morebits.quickForm.element.prototype.render = function QuickFormElementRender(internal_subgroup_id) {
var currentNode = this.compute(this.data, internal_subgroup_id);
for (var i = 0; i < this.childs.length; ++i) {
// do not pass internal_subgroup_id to recursive calls
currentNode[1].appendChild(this.childs[i].render());
}
return currentNode[0];
};
Morebits.quickForm.element.prototype.compute = function QuickFormElementCompute(data, in_id) {
var node;
var childContainder = null;
var label;
var id = (in_id ? in_id + '_' : '') + 'node_' + this.id;
if (data.adminonly && !Morebits.userIsSysop) {
// hell hack alpha
data.type = 'hidden';
}
var i, current, subnode;
switch (data.type) {
case 'form':
node = document.createElement('form');
node.className = 'quickform';
node.setAttribute('action', 'javascript:void(0);');
if (data.event) {
node.addEventListener(data.eventType || 'submit', data.event, false);
}
break;
case 'fragment':
node = document.createDocumentFragment();
// fragments can't have any attributes, so just return it straight away
return [ node, node ];
case 'select':
node = document.createElement('div');
node.setAttribute('id', 'div_' + id);
if (data.label) {
label = node.appendChild(document.createElement('label'));
label.setAttribute('for', id);
label.appendChild(document.createTextNode(data.label));
}
var select = node.appendChild(document.createElement('select'));
if (data.event) {
select.addEventListener('change', data.event, false);
}
if (data.multiple) {
select.setAttribute('multiple', 'multiple');
}
if (data.size) {
select.setAttribute('size', data.size);
}
if (data.disabled) {
select.setAttribute('disabled', 'disabled');
}
select.setAttribute('name', data.name);
if (data.list) {
for (i = 0; i < data.list.length; ++i) {
current = data.list[i];
if (current.list) {
current.type = 'optgroup';
} else {
current.type = 'option';
}
subnode = this.compute(current);
select.appendChild(subnode[0]);
}
}
childContainder = select;
break;
case 'option':
node = document.createElement('option');
node.values = data.value;
node.setAttribute('value', data.value);
if (data.selected) {
node.setAttribute('selected', 'selected');
}
if (data.disabled) {
node.setAttribute('disabled', 'disabled');
}
node.setAttribute('label', data.label);
node.appendChild(document.createTextNode(data.label));
break;
case 'optgroup':
node = document.createElement('optgroup');
node.setAttribute('label', data.label);
if (data.list) {
for (i = 0; i < data.list.length; ++i) {
current = data.list[i];
current.type = 'option'; // must be options here
subnode = this.compute(current);
node.appendChild(subnode[0]);
}
}
break;
case 'field':
node = document.createElement('fieldset');
label = node.appendChild(document.createElement('legend'));
label.appendChild(document.createTextNode(data.label));
if (data.name) {
node.setAttribute('name', data.name);
}
if (data.disabled) {
node.setAttribute('disabled', 'disabled');
}
break;
case 'checkbox':
case 'radio':
node = document.createElement('div');
if (data.list) {
for (i = 0; i < data.list.length; ++i) {
var cur_id = id + '_' + i;
current = data.list[i];
var cur_div;
if (current.type === 'header') {
// inline hack
cur_div = node.appendChild(document.createElement('h6'));
cur_div.appendChild(document.createTextNode(current.label));
if (current.tooltip) {
Morebits.quickForm.element.generateTooltip(cur_div, current);
}
continue;
}
cur_div = node.appendChild(document.createElement('div'));
subnode = cur_div.appendChild(document.createElement('input'));
subnode.values = current.value;
subnode.setAttribute('value', current.value);
subnode.setAttribute('name', current.name || data.name);
subnode.setAttribute('type', data.type);
subnode.setAttribute('id', cur_id);
if (current.checked) {
subnode.setAttribute('checked', 'checked');
}
if (current.disabled) {
subnode.setAttribute('disabled', 'disabled');
}
label = cur_div.appendChild(document.createElement('label'));
label.appendChild(document.createTextNode(current.label));
label.setAttribute('for', cur_id);
if (current.tooltip) {
Morebits.quickForm.element.generateTooltip(label, current);
}
// styles go on the label, doesn't make sense to style a checkbox/radio
if (current.style) {
label.setAttribute('style', current.style);
}
var event;
if (current.subgroup) {
var tmpgroup = current.subgroup;
if (!Array.isArray(tmpgroup)) {
tmpgroup = [ tmpgroup ];
}
var subgroupRaw = new Morebits.quickForm.element({
type: 'div',
id: id + '_' + i + '_subgroup'
});
$.each(tmpgroup, function(idx, el) {
var newEl = $.extend({}, el);
if (!newEl.type) {
newEl.type = data.type;
}
newEl.name = (current.name || data.name) + '.' + newEl.name;
subgroupRaw.append(newEl);
});
var subgroup = subgroupRaw.render(cur_id);
subgroup.className = 'quickformSubgroup';
subnode.subgroup = subgroup;
subnode.shown = false;
event = function(e) {
if (e.target.checked) {
e.target.parentNode.appendChild(e.target.subgroup);
if (e.target.type === 'radio') {
var name = e.target.name;
if (e.target.form.names[name] !== undefined) {
e.target.form.names[name].parentNode.removeChild(e.target.form.names[name].subgroup);
}
e.target.form.names[name] = e.target;
}
} else {
e.target.parentNode.removeChild(e.target.subgroup);
}
};
subnode.addEventListener('change', event, true);
if (current.checked) {
subnode.parentNode.appendChild(subgroup);
}
} else if (data.type === 'radio') {
event = function(e) {
if (e.target.checked) {
var name = e.target.name;
if (e.target.form.names[name] !== undefined) {
e.target.form.names[name].parentNode.removeChild(e.target.form.names[name].subgroup);
}
delete e.target.form.names[name];
}
};
subnode.addEventListener('change', event, true);
}
// add users' event last, so it can interact with the subgroup
if (data.event) {
subnode.addEventListener('change', data.event, false);
} else if (current.event) {
subnode.addEventListener('change', current.event, true);
}
}
}
break;
case 'input':
node = document.createElement('div');
node.setAttribute('id', 'div_' + id);
if (data.label) {
label = node.appendChild(document.createElement('label'));
label.appendChild(document.createTextNode(data.label));
label.setAttribute('for', data.id || id);
}
subnode = node.appendChild(document.createElement('input'));
if (data.value) {
subnode.setAttribute('value', data.value);
}
subnode.setAttribute('name', data.name);
subnode.setAttribute('type', 'text');
if (data.size) {
subnode.setAttribute('size', data.size);
}
if (data.disabled) {
subnode.setAttribute('disabled', 'disabled');
}
if (data.required) {
subnode.setAttribute('required', 'required');
}
if (data.readonly) {
subnode.setAttribute('readonly', 'readonly');
}
if (data.maxlength) {
subnode.setAttribute('maxlength', data.maxlength);
}
if (data.event) {
subnode.addEventListener('keyup', data.event, false);
}
childContainder = subnode;
break;
case 'dyninput':
var min = data.min || 1;
var max = data.max || Infinity;
node = document.createElement('div');
label = node.appendChild(document.createElement('h5'));
label.appendChild(document.createTextNode(data.label));
var listNode = node.appendChild(document.createElement('div'));
var more = this.compute({
type: 'button',
label: 'more',
disabled: min >= max,
event: function(e) {
var new_node = new Morebits.quickForm.element(e.target.sublist);
e.target.area.appendChild(new_node.render());
if (++e.target.counter >= e.target.max) {
e.target.setAttribute('disabled', 'disabled');
}
e.stopPropagation();
}
});
node.appendChild(more[0]);
var moreButton = more[1];
var sublist = {
type: '_dyninput_element',
label: data.sublabel || data.label,
name: data.name,
value: data.value,
size: data.size,
remove: false,
maxlength: data.maxlength,
event: data.event
};
for (i = 0; i < min; ++i) {
var elem = new Morebits.quickForm.element(sublist);
listNode.appendChild(elem.render());
}
sublist.remove = true;
sublist.morebutton = moreButton;
sublist.listnode = listNode;
moreButton.sublist = sublist;
moreButton.area = listNode;
moreButton.max = max - min;
moreButton.counter = 0;
break;
case '_dyninput_element': // Private, similar to normal input
node = document.createElement('div');
if (data.label) {
label = node.appendChild(document.createElement('label'));
label.appendChild(document.createTextNode(data.label));
label.setAttribute('for', id);
}
subnode = node.appendChild(document.createElement('input'));
if (data.value) {
subnode.setAttribute('value', data.value);
}
subnode.setAttribute('name', data.name);
subnode.setAttribute('type', 'text');
if (data.size) {
subnode.setAttribute('size', data.size);
}
if (data.maxlength) {
subnode.setAttribute('maxlength', data.maxlength);
}
if (data.event) {
subnode.addEventListener('keyup', data.event, false);
}
if (data.remove) {
var remove = this.compute({
type: 'button',
label: 'remove',
event: function(e) {
var list = e.target.listnode;
var node = e.target.inputnode;
var more = e.target.morebutton;
list.removeChild(node);
--more.counter;
more.removeAttribute('disabled');
e.stopPropagation();
}
});
node.appendChild(remove[0]);
var removeButton = remove[1];
removeButton.inputnode = node;
removeButton.listnode = data.listnode;
removeButton.morebutton = data.morebutton;
}
break;
case 'hidden':
node = document.createElement('input');
node.setAttribute('type', 'hidden');
node.values = data.value;
node.setAttribute('value', data.value);
node.setAttribute('name', data.name);
break;
case 'header':
node = document.createElement('h5');
node.appendChild(document.createTextNode(data.label));
break;
case 'div':
node = document.createElement('div');
if (data.name) {
node.setAttribute('name', data.name);
}
if (data.label) {
if (!Array.isArray(data.label)) {
data.label = [ data.label ];
}
var result = document.createElement('span');
result.className = 'quickformDescription';
for (i = 0; i < data.label.length; ++i) {
if (typeof data.label[i] === 'string') {
result.appendChild(document.createTextNode(data.label[i]));
} else if (data.label[i] instanceof Element) {
result.appendChild(data.label[i]);
}
}
node.appendChild(result);
}
break;
case 'submit':
node = document.createElement('span');
childContainder = node.appendChild(document.createElement('input'));
childContainder.setAttribute('type', 'submit');
if (data.label) {
childContainder.setAttribute('value', data.label);
}
childContainder.setAttribute('name', data.name || 'submit');
if (data.disabled) {
childContainder.setAttribute('disabled', 'disabled');
}
break;
case 'button':
node = document.createElement('span');
childContainder = node.appendChild(document.createElement('input'));
childContainder.setAttribute('type', 'button');
if (data.label) {
childContainder.setAttribute('value', data.label);
}
childContainder.setAttribute('name', data.name);
if (data.disabled) {
childContainder.setAttribute('disabled', 'disabled');
}
if (data.event) {
childContainder.addEventListener('click', data.event, false);
}
break;
case 'textarea':
node = document.createElement('div');
node.setAttribute('id', 'div_' + id);
if (data.label) {
label = node.appendChild(document.createElement('h5'));
var labelElement = document.createElement('label');
labelElement.textContent = data.label;
labelElement.setAttribute('for', data.id || id);
label.appendChild(labelElement);
}
subnode = node.appendChild(document.createElement('textarea'));
subnode.setAttribute('name', data.name);
if (data.cols) {
subnode.setAttribute('cols', data.cols);
}
if (data.rows) {
subnode.setAttribute('rows', data.rows);
}
if (data.disabled) {
subnode.setAttribute('disabled', 'disabled');
}
if (data.required) {
subnode.setAttribute('required', 'required');
}
if (data.readonly) {
subnode.setAttribute('readonly', 'readonly');
}
if (data.value) {
subnode.value = data.value;
}
childContainder = subnode;
break;
default:
throw new Error('Morebits.quickForm: unknown element type ' + data.type.toString());
}
if (!childContainder) {
childContainder = node;
}
if (data.tooltip) {
Morebits.quickForm.element.generateTooltip(label || node, data);
}
if (data.extra) {
childContainder.extra = data.extra;
}
if (data.style) {
childContainder.setAttribute('style', data.style);
}
if (data.className) {
childContainder.className = childContainder.className ?
childContainder.className + ' ' + data.className :
data.className;
}
childContainder.setAttribute('id', data.id || id);
return [ node, childContainder ];
};
Morebits.quickForm.element.autoNWSW = function() {
return $(this).offset().top > ($(document).scrollTop() + ($(window).height() / 2)) ? 'sw' : 'nw';
};
/**
* Create a jquery.tipsy-based tooltip.
* @requires jquery.tipsy
* @param {HTMLElement} node - the HTML element beside which a tooltip is to be generated
* @param {Object} data - tooltip-related configuration data
*/
Morebits.quickForm.element.generateTooltip = function QuickFormElementGenerateTooltip(node, data) {
$('<span/>', {
'class': 'ui-icon ui-icon-help ui-icon-inline morebits-tooltip'
}).appendTo(node);
};
// Some utility methods for manipulating quickForms after their creation:
// (None of these work for "dyninput" type fields at present)
/**
* Returns all form elements with a given field name or ID
* @param {HTMLFormElement} form
* @param {string} fieldName - the name or id of the fields
* @returns {HTMLElement[]} - array of matching form elements
*/
Morebits.quickForm.getElements = function QuickFormGetElements(form, fieldName) {
var $form = $(form);
var $elements = $form.find('[name="' + fieldName + '"]');
if ($elements.length > 0) {
return $elements.toArray();
}
$elements = $form.find('#' + fieldName);
if ($elements.length > 0) {
return $elements.toArray();
}
return null;
};
/**
* Searches the array of elements for a checkbox or radio button with a certain
* `value` attribute, and returns the first such element. Returns null if not found.
* @param {HTMLInputElement[]} elementArray - array of checkbox or radio elements
* @param {string} value - value to search for
* @returns {HTMLInputElement}
*/
Morebits.quickForm.getCheckboxOrRadio = function QuickFormGetCheckboxOrRadio(elementArray, value) {
var found = $.grep(elementArray, function(el) {
return el.value === value;
});
if (found.length > 0) {
return found[0];
}
return null;
};
/**
* Returns the <div> containing the form element, or the form element itself
* May not work as expected on checkboxes or radios
* @param {HTMLElement} element
* @returns {HTMLElement}
*/
Morebits.quickForm.getElementContainer = function QuickFormGetElementContainer(element) {
// for divs, headings and fieldsets, the container is the element itself
if (element instanceof HTMLFieldSetElement || element instanceof HTMLDivElement ||
element instanceof HTMLHeadingElement) {
return element;
}
// for others, just return the parent node
return element.parentNode;
};
/**
* Gets the HTML element that contains the label of the given form element
* (mainly for internal use)
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @returns {HTMLElement}
*/
Morebits.quickForm.getElementLabelObject = function QuickFormGetElementLabelObject(element) {
// for buttons, divs and headers, the label is on the element itself
if (element.type === 'button' || element.type === 'submit' ||
element instanceof HTMLDivElement || element instanceof HTMLHeadingElement) {
return element;
// for fieldsets, the label is the child <legend> element
} else if (element instanceof HTMLFieldSetElement) {
return element.getElementsByTagName('legend')[0];
// for textareas, the label is the sibling <h5> element
} else if (element instanceof HTMLTextAreaElement) {
return element.parentNode.getElementsByTagName('h5')[0];
}
// for others, the label is the sibling <label> element
return element.parentNode.getElementsByTagName('label')[0];
};
/**
* Gets the label text of the element
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @returns {string}
*/
Morebits.quickForm.getElementLabel = function QuickFormGetElementLabel(element) {
var labelElement = Morebits.quickForm.getElementLabelObject(element);
if (!labelElement) {
return null;
}
return labelElement.firstChild.textContent;
};
/**
* Sets the label of the element to the given text
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @param {string} labelText
* @returns {boolean} true if succeeded, false if the label element is unavailable
*/
Morebits.quickForm.setElementLabel = function QuickFormSetElementLabel(element, labelText) {
var labelElement = Morebits.quickForm.getElementLabelObject(element);
if (!labelElement) {
return false;
}
labelElement.firstChild.textContent = labelText;
return true;
};
/**
* Stores the element's current label, and temporarily sets the label to the given text
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @param {string} temporaryLabelText
* @returns {boolean} true if succeeded, false if the label element is unavailable
*/
Morebits.quickForm.overrideElementLabel = function QuickFormOverrideElementLabel(element, temporaryLabelText) {
if (!element.hasAttribute('data-oldlabel')) {
element.setAttribute('data-oldlabel', Morebits.quickForm.getElementLabel(element));
}
return Morebits.quickForm.setElementLabel(element, temporaryLabelText);
};
/**
* Restores the label stored by overrideElementLabel
* @param {(HTMLElement|Morebits.quickForm.element)} element
* @returns {boolean} true if succeeded, false if the label element is unavailable
*/
Morebits.quickForm.resetElementLabel = function QuickFormResetElementLabel(element) {
if (element.hasAttribute('data-oldlabel')) {
return Morebits.quickForm.setElementLabel(element, element.getAttribute('data-oldlabel'));
}
return null;
};
/**
* Shows or hides a form element plus its label and tooltip
* @param {(HTMLElement|jQuery|string)} element HTML/jQuery element, or jQuery selector string
* @param {boolean} [visibility] Skip this to toggle visibility
*/
Morebits.quickForm.setElementVisibility = function QuickFormSetElementVisibility(element, visibility) {
$(element).toggle(visibility);
};
/**
* Shows or hides the "question mark" icon (which displays the tooltip) next to a form element
* @param {(HTMLElement|jQuery)} element
* @param {boolean} [visibility] Skip this to toggle visibility
*/
Morebits.quickForm.setElementTooltipVisibility = function QuickFormSetElementTooltipVisibility(element, visibility) {
$(Morebits.quickForm.getElementContainer(element)).find('.morebits-tooltip').toggle(visibility);
};
/**
* **************** HTMLFormElement ****************
*/
/**
* Returns an array containing the values of elements with the given name, that has it's
* checked property set to true. (i.e. a checkbox or a radiobutton is checked), or select
* options that have selected set to true. (don't try to mix selects with radio/checkboxes,
* please)
* Type is optional and can specify if either radio or checkbox (for the event
* that both checkboxes and radiobuttons have the same name.
*
* XXX: Doesn't seem to work reliably across all browsers at the moment. -- see getChecked2
* in twinkleunlink.js, which is better
*/
HTMLFormElement.prototype.getChecked = function(name, type) {
var elements = this.elements[name];
if (!elements) {
// if the element doesn't exists, return null.
return null;
}
var return_array = [];
var i;
if (elements instanceof HTMLSelectElement) {
var options = elements.options;
for (i = 0; i < options.length; ++i) {
if (options[i].selected) {
if (options[i].values) {
return_array.push(options[i].values);
} else {
return_array.push(options[i].value);
}
}
}
} else if (elements instanceof HTMLInputElement) {
if (type && elements.type !== type) {
return [];
} else if (elements.checked) {
return [ elements.value ];
}
} else {
for (i = 0; i < elements.length; ++i) {
if (elements[i].checked) {
if (type && elements[i].type !== type) {
continue;
}
if (elements[i].values) {
return_array.push(elements[i].values);
} else {
return_array.push(elements[i].value);
}
}
}
}
return return_array;
};
/**
* getUnchecked:
* Does the same as getChecked above, but with unchecked elements.
*/
HTMLFormElement.prototype.getUnchecked = function(name, type) {
var elements = this.elements[name];
if (!elements) {
// if the element doesn't exists, return null.
return null;
}
var return_array = [];
var i;
if (elements instanceof HTMLSelectElement) {
var options = elements.options;
for (i = 0; i < options.length; ++i) {
if (!options[i].selected) {
if (options[i].values) {
return_array.push(options[i].values);
} else {
return_array.push(options[i].value);
}
}
}
} else if (elements instanceof HTMLInputElement) {
if (type && elements.type !== type) {
return [];
} else if (!elements.checked) {
return [ elements.value ];
}
} else {
for (i = 0; i < elements.length; ++i) {
if (!elements[i].checked) {
if (type && elements[i].type !== type) {
continue;
}
if (elements[i].values) {
return_array.push(elements[i].values);
} else {
return_array.push(elements[i].value);
}
}
}
}
return return_array;
};
/**
* **************** RegExp ****************
*
* Escapes a string to be used in a RegExp
* @param {string} text - string to be escaped
* @param {boolean} [space_fix=false] - Set true to replace spaces and underscores with `[ _]` as they are
* often equivalent
* @returns {string} - the escaped text
*/
RegExp.escape = function(text, space_fix) {
text = mw.util.escapeRegExp(text);
// Special MediaWiki escape - underscore/space are often equivalent
if (space_fix) {
text = text.replace(/ |_/g, '[_ ]');
}
return text;
};
/**
* **************** String; Morebits.string ****************
*/
Morebits.string = {
// Helper functions to change case of a string
toUpperCaseFirstChar: function(str) {
str = str.toString();
return str.substr(0, 1).toUpperCase() + str.substr(1);
},
toLowerCaseFirstChar: function(str) {
str = str.toString();
return str.substr(0, 1).toLowerCase() + str.substr(1);
},
/**
* Gives an array of substrings of `str` starting with `start` and
* ending with `end`, which is not in `skiplist`
* @param {string} str
* @param {string} start
* @param {string} end
* @param {(string[]|string)} [skiplist]
* @returns {String[]}
*/
splitWeightedByKeys: function(str, start, end, skiplist) {
if (start.length !== end.length) {
throw new Error('start marker and end marker must be of the same length');
}
var level = 0;
var initial = null;
var result = [];
if (!Array.isArray(skiplist)) {
if (skiplist === undefined) {
skiplist = [];
} else if (typeof skiplist === 'string') {
skiplist = [ skiplist ];
} else {
throw new Error('non-applicable skiplist parameter');
}
}
for (var i = 0; i < str.length; ++i) {
for (var j = 0; j < skiplist.length; ++j) {
if (str.substr(i, skiplist[j].length) === skiplist[j]) {
i += skiplist[j].length - 1;
continue;
}
}
if (str.substr(i, start.length) === start) {
if (initial === null) {
initial = i;
}
++level;
i += start.length - 1;
} else if (str.substr(i, end.length) === end) {
--level;
i += end.length - 1;
}
if (!level && initial !== null) {
result.push(str.substring(initial, i + 1));
initial = null;
}
}
return result;
},
/**
* Formats freeform "reason" (from a textarea) for deletion/other templates
* that are going to be substituted, (e.g. PROD, XFD, RPP)
* @param {string} str
* @returns {string}
*/
formatReasonText: function(str) {
var result = str.toString().trim();
var unbinder = new Morebits.unbinder(result);
unbinder.unbind('<no' + 'wiki>', '</no' + 'wiki>');
unbinder.content = unbinder.content.replace(/\|/g, '{{subst:!}}');
return unbinder.rebind();
},
/**
* Like `String.prototype.replace()`, but escapes any dollar signs in the replacement string.
* Useful when the the replacement string is arbitrary, such as a username or freeform user input,
* and could contain dollar signs.
* @param {string} string - text in which to replace
* @param {(string|RegExp)} pattern
* @param {string} replacement
* @returns {string}
*/
safeReplace: function morebitsStringSafeReplace(string, pattern, replacement) {
return string.replace(pattern, replacement.replace(/\$/g, '$$$$'));
}
};
/**
* **************** Morebits.array ****************
*/
Morebits.array = {
/**
* @returns {Array} a copy of the array with duplicates removed
*/
uniq: function(arr) {
if (!Array.isArray(arr)) {
throw 'A non-array object passed to Morebits.array.uniq';
}
var result = [];
for (var i = 0; i < arr.length; ++i) {
var current = arr[i];
if (result.indexOf(current) === -1) {
result.push(current);
}
}
return result;
},
/**
* @returns {Array} a copy of the array with the first instance of each value
* removed; subsequent instances of those values (duplicates) remain
*/
dups: function(arr) {
if (!Array.isArray(arr)) {
throw 'A non-array object passed to Morebits.array.dups';
}
var uniques = [];
var result = [];
for (var i = 0; i < arr.length; ++i) {
var current = arr[i];
if (uniques.indexOf(current) === -1) {
uniques.push(current);
} else {
result.push(current);
}
}
return result;
},
/**
* Break up an array into smaller arrays.
* @param {Array} arr
* @param {number} size - Size of each chunk (except the last, which could be different)
* @returns {Array} an array of these smaller arrays
*/
chunk: function(arr, size) {
if (!Array.isArray(arr)) {
throw 'A non-array object passed to Morebits.array.chunk';
}
if (typeof size !== 'number' || size <= 0) { // pretty impossible to do anything :)
return [ arr ]; // we return an array consisting of this array.
}
var result = [];
var current;
for (var i = 0; i < arr.length; ++i) {
if (i % size === 0) { // when 'i' is 0, this is always true, so we start by creating one.
current = [];
result.push(current);
}
current.push(arr[i]);
}
return result;
}
};
/**
* ************ Morebits.select2 ***************
* Utilities to enhance select2 menus
* See twinklewarn, twinklexfd, twinkleblock for sample usages
*/
Morebits.select2 = {
matchers: {
/**
* Custom matcher in which if the optgroup name matches, all options in that
* group are shown, like in jquery.chosen
*/
optgroupFull: function(params, data) {
var originalMatcher = $.fn.select2.defaults.defaults.matcher;
var result = originalMatcher(params, data);
if (result && params.term &&
data.text.toUpperCase().indexOf(params.term.toUpperCase()) !== -1) {
result.children = data.children;
}
return result;
},
/** Custom matcher that matches from the beginning of words only */
wordBeginning: function(params, data) {
var originalMatcher = $.fn.select2.defaults.defaults.matcher;
var result = originalMatcher(params, data);
if (!params.term || (result &&
new RegExp('\\b' + mw.util.escapeRegExp(params.term), 'i').test(result.text))) {
return result;
}
return null;
}
},
/** Underline matched part of options */
highlightSearchMatches: function(data) {
var searchTerm = Morebits.select2SearchQuery;
if (!searchTerm || data.loading) {
return data.text;
}
var idx = data.text.toUpperCase().indexOf(searchTerm.toUpperCase());
if (idx < 0) {
return data.text;
}
return $('<span>').append(
data.text.slice(0, idx),
$('<span>').css('text-decoration', 'underline').text(data.text.slice(idx, idx + searchTerm.length)),
data.text.slice(idx + searchTerm.length)
);
},
/** Intercept query as it is happening, for use in highlightSearchMatches */
queryInterceptor: function(params) {
Morebits.select2SearchQuery = params && params.term;
},
/**
* Open dropdown and begin search when the .select2-selection has focus and a key is pressed
* https://github.com/select2/select2/issues/3279#issuecomment-442524147
*/
autoStart: function(ev) {
if (ev.which < 48) {
return;
}
var target = $(ev.target).closest('.select2-container');
if (!target.length) {
return;
}
target = target.prev();
target.select2('open');
var search = target.data('select2').dropdown.$search ||
target.data('select2').selection.$search;
search.focus();
}
};
/**
* **************** Morebits.pageNameNorm ****************
* Stores a normalized version of the wgPageName variable (underscores converted to spaces).
* For queen/king/whatever and country!
*/
Morebits.pageNameNorm = mw.config.get('wgPageName').replace(/_/g, ' ');
/**
* *************** Morebits.pageNameRegex *****************
* For a page name 'Foo bar', returns the string '[Ff]oo bar'
* @param {string} pageName - page name without namespace
* @returns {string}
*/
Morebits.pageNameRegex = function(pageName) {
return '[' + pageName[0].toUpperCase() + pageName[0].toLowerCase() + ']' + pageName.slice(1);
};
/**
* **************** Morebits.unbinder ****************
* Used for temporarily hiding a part of a string while processing the rest of it.
*
* eg. var u = new Morebits.unbinder("Hello world <!-- world --> world");
* u.unbind('<!--','-->');
* u.content = u.content.replace(/world/g, 'earth');
* u.rebind(); // gives "Hello earth <!-- world --> earth"
*
* Text within the 'unbinded' part (in this case, the HTML comment) remains intact
* unbind() can be called multiple times to unbind multiple parts of the string.
*
* Used by Morebits.wikitext.page.commentOutImage
*/
/**
* @constructor
* @param {string} string
*/
Morebits.unbinder = function Unbinder(string) {
if (typeof string !== 'string') {
throw new Error('not a string');
}
this.content = string;
this.counter = 0;
this.history = {};
this.prefix = '%UNIQ::' + Math.random() + '::';
this.postfix = '::UNIQ%';
};
Morebits.unbinder.prototype = {
/**
* @param {string} prefix
* @param {string} postfix
*/
unbind: function UnbinderUnbind(prefix, postfix) {
var re = new RegExp(prefix + '([\\s\\S]*?)' + postfix, 'g');
this.content = this.content.replace(re, Morebits.unbinder.getCallback(this));
},
/** @returns {string} The output */
rebind: function UnbinderRebind() {
var content = this.content;
content.self = this;
for (var current in this.history) {
if (Object.prototype.hasOwnProperty.call(this.history, current)) {
content = content.replace(current, this.history[current]);
}
}
return content;
},
prefix: null, // %UNIQ::0.5955981644938324::
postfix: null, // ::UNIQ%
content: null, // string
counter: null, // 0++
history: null // {}
};
Morebits.unbinder.getCallback = function UnbinderGetCallback(self) {
return function UnbinderCallback(match) {
var current = self.prefix + self.counter + self.postfix;
self.history[current] = match;
++self.counter;
return current;
};
};
/**
* **************** Date ****************
* Helper functions to get the month as a string instead of a number
*
* @deprecated Since early 2020 in favor of Morebits.date (#814)
*/
Date.monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December' ];
Date.monthNamesAbbrev = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
Date.prototype.getUTCMonthName = function() {
console.warn("NOTE: Date prototypes from Twinkle's Morebits (such as getUTCMonthName) have been deprecated, use Morebits.date instead"); // eslint-disable-line no-console
return Date.monthNames[this.getUTCMonth()];
};
Date.prototype.getUTCMonthNameAbbrev = function() {
console.warn("NOTE: Date prototypes from Twinkle's Morebits (such as getUTCMonthNameAbbrev) have been deprecated, use Morebits.date instead"); // eslint-disable-line no-console
return Date.monthNamesAbbrev[this.getUTCMonth()];
};
/**
* **************** Morebits.date ****************
*/
/**
* @constructor
* Create a date object. MediaWiki timestamp format is also acceptable,
* in addition to everything that JS Date() accepts.
*/
Morebits.date = function() {
var args = Array.prototype.slice.call(arguments);
if (typeof args[0] === 'string') {
// Attempt to remove a comma and paren-wrapped timezone, to get MediaWiki timestamps to parse
// Firefox (at least in 75) seems to be okay with the comma, though
args[0] = args[0].replace(/(\d\d:\d\d),/, '$1').replace(/\(UTC\)/, 'UTC');
}
this._d = new (Function.prototype.bind.apply(Date, [Date].concat(args)));
};
Morebits.date.localeData = {
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
relativeTimes: {
thisDay: '[Today at] h:mm A',
prevDay: '[Yesterday at] h:mm A',
nextDay: '[Tomorrow at] h:mm A',
thisWeek: 'dddd [at] h:mm A',
pastWeek: '[Last] dddd [at] h:mm A',
other: 'YYYY-MM-DD'
}
};
// Allow native Date.prototype methods to be used on Morebits.date objects
Object.getOwnPropertyNames(Date.prototype).forEach(function(func) {
Morebits.date.prototype[func] = function() {
return this._d[func].apply(this._d, Array.prototype.slice.call(arguments));
};
});
$.extend(Morebits.date.prototype, {
isValid: function() {
return !isNaN(this.getTime());
},
/** @param {(Date|Morebits.date)} date */
isBefore: function(date) {
return this.getTime() < date.getTime();
},
isAfter: function(date) {
return this.getTime() > date.getTime();
},
/** @return {string} */
getUTCMonthName: function() {
return Morebits.date.localeData.months[this.getUTCMonth()];
},
getUTCMonthNameAbbrev: function() {
return Morebits.date.localeData.monthsShort[this.getUTCMonth()];
},
getMonthName: function() {
return Morebits.date.localeData.months[this.getMonth()];
},
getMonthNameAbbrev: function() {
return Morebits.date.localeData.monthsShort[this.getMonth()];
},
getUTCDayName: function() {
return Morebits.date.localeData.days[this.getUTCDay()];
},
getUTCDayNameAbbrev: function() {
return Morebits.date.localeData.daysShort[this.getUTCDay()];
},
getDayName: function() {
return Morebits.date.localeData.days[this.getDay()];
},
getDayNameAbbrev: function() {
return Morebits.date.localeData.daysShort[this.getDay()];
},
/**
* Add a given number of minutes, hours, days, months or years to the date.
* This is done in-place. The modified date object is also returned, allowing chaining.
* @param {number} number - should be an integer
* @param {string} unit
* @throws {Error} if invalid or unsupported unit is given
* @returns {Morebits.date}
*/
add: function(number, unit) {
// mapping time units with getter/setter function names
var unitMap = {
seconds: 'Seconds',
minutes: 'Minutes',
hours: 'Hours',
days: 'Date',
months: 'Month',
years: 'FullYear'
};
var unitNorm = unitMap[unit] || unitMap[unit + 's']; // so that both singular and plural forms work
if (unitNorm) {
this['set' + unitNorm](this['get' + unitNorm]() + number);
return this;
}
throw new Error('Invalid unit "' + unit + '": Only ' + Object.keys(unitMap).join(', ') + ' are allowed.');
},
/**
* Subtracts a given number of minutes, hours, days, months or years to the date.
* This is done in-place. The modified date object is also returned, allowing chaining.
* @param {number} number - should be an integer
* @param {string} unit
* @throws {Error} if invalid or unsupported unit is given
* @returns {Morebits.date}
*/
subtract: function(number, unit) {
return this.add(-number, unit);
},
/**
* Formats the date into a string per the given format string.
* Replacement syntax is a subset of that in moment.js.
* @param {string} formatstr
* @param {(string|number)} [zone=system] - 'system' (for browser-default time zone),
* 'utc' (for UTC), or specify a time zone as number of minutes past UTC.
* @returns {string}
*/
format: function(formatstr, zone) {
var udate = this;
// create a new date object that will contain the date to display as system time
if (zone === 'utc') {
udate = new Morebits.date(this.getTime()).add(this.getTimezoneOffset(), 'minutes');
} else if (typeof zone === 'number') {
// convert to utc, then add the utc offset given
udate = new Morebits.date(this.getTime()).add(this.getTimezoneOffset() + zone, 'minutes');
}
var pad = function(num) {
return num < 10 ? '0' + num : num;
};
var h24 = udate.getHours(), m = udate.getMinutes(), s = udate.getSeconds();
var D = udate.getDate(), M = udate.getMonth() + 1, Y = udate.getFullYear();
var h12 = h24 % 12 || 12, amOrPm = h24 >= 12 ? 'PM' : 'AM';
var replacementMap = {
'HH': pad(h24), 'H': h24, 'hh': pad(h12), 'h': h12, 'A': amOrPm,
'mm': pad(m), 'm': m,
'ss': pad(s), 's': s,
'dddd': udate.getDayName(), 'ddd': udate.getDayNameAbbrev(), 'd': udate.getDay(),
'DD': pad(D), 'D': D,
'MMMM': udate.getMonthName(), 'MMM': udate.getMonthNameAbbrev(), 'MM': pad(M), 'M': M,
'YYYY': Y, 'YY': pad(Y % 100), 'Y': Y
};
var unbinder = new Morebits.unbinder(formatstr); // escape stuff between [...]
unbinder.unbind('\\[', '\\]');
unbinder.content = unbinder.content.replace(
/* Regex notes:
* d(d{2,3})? matches exactly 1, 3 or 4 occurrences of 'd' ('dd' is treated as a double match of 'd')
* Y{1,2}(Y{2})? matches exactly 1, 2 or 4 occurrences of 'Y'
*/
/H{1,2}|h{1,2}|m{1,2}|s{1,2}|d(d{2,3})?|D{1,2}|M{1,4}|Y{1,2}(Y{2})?|A/g,
function(match) {
return replacementMap[match];
}
);
return unbinder.rebind().replace(/\[(.*?)\]/g, '$1');
},
/**
* Gives a readable relative time string such as "Yesterday at 6:43 PM" or "Last Thursday at 11:45 AM".
* Similar to calendar in moment.js, but with time zone support.
* @param {(string|number)} [zone=system] - 'system' (for browser-default time zone),
* 'utc' (for UTC), or specify a time zone as number of minutes past UTC
* @returns {string}
*/
calendar: function(zone) {
// Zero out the hours, minutes, seconds and milliseconds - keeping only the date;
// find the difference. Note that setHours() returns the same thing as getTime().
var dateDiff = (new Date().setHours(0, 0, 0, 0) -
new Date(this).setHours(0, 0, 0, 0)) / 8.64e7;
switch (true) {
case dateDiff === 0:
return this.format(Morebits.date.localeData.relativeTimes.thisDay, zone);
case dateDiff === 1:
return this.format(Morebits.date.localeData.relativeTimes.prevDay, zone);
case dateDiff > 0 && dateDiff < 7:
return this.format(Morebits.date.localeData.relativeTimes.pastWeek, zone);
case dateDiff === -1:
return this.format(Morebits.date.localeData.relativeTimes.nextDay, zone);
case dateDiff < 0 && dateDiff > -7:
return this.format(Morebits.date.localeData.relativeTimes.thisWeek, zone);
default:
return this.format(Morebits.date.localeData.relativeTimes.other, zone);
}
},
/**
* @returns {RegExp} that matches wikitext section titles such as ==December 2019== or
* === Jan 2018 ===
*/
monthHeaderRegex: function() {
return new RegExp('^==+\\s*(?:' + this.getUTCMonthName() + '|' + this.getUTCMonthNameAbbrev() +
')\\s+' + this.getUTCFullYear() + '\\s*==+', 'mg');
},
/**
* Creates a wikitext section header with the month and year.
* @param {number} [level=2] - Header level (default 2)
* @returns {string}
*/
monthHeader: function(level) {
level = level || 2;
var header = Array(level + 1).join('='); // String.prototype.repeat not supported in IE 11
return header + ' ' + this.getUTCMonthName() + ' ' + this.getUTCFullYear() + ' ' + header;
}
});
/**
* **************** Morebits.wiki ****************
* Various objects for wiki editing and API access
*/
Morebits.wiki = {};
/**
* Determines whether the current page is a redirect or soft redirect
* (fails to detect soft redirects on edit, history, etc. pages)
* @returns {boolean}
*/
Morebits.wiki.isPageRedirect = function wikipediaIsPageRedirect() {
return !!(mw.config.get('wgIsRedirect') || document.getElementById('softredirect'));
};
/**
* **************** Morebits.wiki.actionCompleted ****************
*
* Use of Morebits.wiki.actionCompleted():
* Every call to Morebits.wiki.api.post() results in the dispatch of
* an asynchronous callback. Each callback can in turn
* make an additional call to Morebits.wiki.api.post() to continue a
* processing sequence. At the conclusion of the final callback
* of a processing sequence, it is not possible to simply return to the
* original caller because there is no call stack leading back to
* the original context. Instead, Morebits.wiki.actionCompleted.event() is
* called to display the result to the user and to perform an optional
* page redirect.
*
* The determination of when to call Morebits.wiki.actionCompleted.event()
* is managed through the globals Morebits.wiki.numberOfActionsLeft and
* Morebits.wiki.nbrOfCheckpointsLeft. Morebits.wiki.numberOfActionsLeft is
* incremented at the start of every Morebits.wiki.api call and decremented
* after the completion of a callback function. If a callback function
* does not create a new Morebits.wiki.api object before exiting, it is the
* final step in the processing chain and Morebits.wiki.actionCompleted.event()
* will then be called.
*
* Optionally, callers may use Morebits.wiki.addCheckpoint() to indicate that
* processing is not complete upon the conclusion of the final callback function.
* This is used for batch operations. The end of a batch is signaled by calling
* Morebits.wiki.removeCheckpoint().
*/
Morebits.wiki.numberOfActionsLeft = 0;
Morebits.wiki.nbrOfCheckpointsLeft = 0;
Morebits.wiki.actionCompleted = function(self) {
if (--Morebits.wiki.numberOfActionsLeft <= 0 && Morebits.wiki.nbrOfCheckpointsLeft <= 0) {
Morebits.wiki.actionCompleted.event(self);
}
};
// Change per action wanted
Morebits.wiki.actionCompleted.event = function() {
if (Morebits.wiki.actionCompleted.notice) {
Morebits.status.actionCompleted(Morebits.wiki.actionCompleted.notice);
}
if (Morebits.wiki.actionCompleted.redirect) {
// if it isn't a URL, make it one. TODO: This breaks on the articles 'http://', 'ftp://', and similar ones.
if (!(/^\w+:\/\//).test(Morebits.wiki.actionCompleted.redirect)) {
Morebits.wiki.actionCompleted.redirect = mw.util.getUrl(Morebits.wiki.actionCompleted.redirect);
if (Morebits.wiki.actionCompleted.followRedirect === false) {
Morebits.wiki.actionCompleted.redirect += '?redirect=no';
}
}
window.setTimeout(function() {
window.location = Morebits.wiki.actionCompleted.redirect;
}, Morebits.wiki.actionCompleted.timeOut);
}
};
Morebits.wiki.actionCompleted.timeOut = typeof window.wpActionCompletedTimeOut === 'undefined' ? 5000 : window.wpActionCompletedTimeOut;
Morebits.wiki.actionCompleted.redirect = null;
Morebits.wiki.actionCompleted.notice = null;
Morebits.wiki.addCheckpoint = function() {
++Morebits.wiki.nbrOfCheckpointsLeft;
};
Morebits.wiki.removeCheckpoint = function() {
if (--Morebits.wiki.nbrOfCheckpointsLeft <= 0 && Morebits.wiki.numberOfActionsLeft <= 0) {
Morebits.wiki.actionCompleted.event();
}
};
/**
* **************** Morebits.wiki.api ****************
* An easy way to talk to the MediaWiki API.
*/
/**
* In new code, the use of the last 3 parameters should be avoided, instead use setStatusElement() to bind the
* status element (if needed) and use .then() or .catch() on the promise returned by post(), rather than specify
* the onSuccess or onFailure callbacks.
* @constructor
* @param {string} currentAction - The current action (required)
* @param {Object} query - The query (required)
* @param {Function} [onSuccess] - The function to call when request gotten
* @param {Morebits.status} [statusElement] - A Morebits.status object to use for status messages (optional)
* @param {Function} [onError] - The function to call if an error occurs (optional)
*/
Morebits.wiki.api = function(currentAction, query, onSuccess, statusElement, onError) {
this.currentAction = currentAction;
this.query = query;
this.query.assert = 'user';
this.onSuccess = onSuccess;
this.onError = onError;
if (statusElement) {
this.setStatusElement(statusElement);
} else {
this.statelem = new Morebits.status(currentAction);
}
if (!query.format) {
this.query.format = 'xml';
} else if (['xml', 'json'].indexOf(query.format) === -1) {
this.statelem.error('Invalid API format: only xml and json are supported.');
}
};
Morebits.wiki.api.prototype = {
currentAction: '',
onSuccess: null,
onError: null,
parent: window, // use global context if there is no parent object
query: null,
response: null,
responseXML: null, // use `response` instead; retained for backwards compatibility
statelem: null, // this non-standard name kept for backwards compatibility
statusText: null, // result received from the API, normally "success" or "error"
errorCode: null, // short text error code, if any, as documented in the MediaWiki API
errorText: null, // full error description, if any
/**
* Keep track of parent object for callbacks
* @param {*} parent
*/
setParent: function(parent) {
this.parent = parent;
},
/** @param {Morebits.status} statusElement */
setStatusElement: function(statusElement) {
this.statelem = statusElement;
this.statelem.status(this.currentAction);
},
/**
* Carries out the request.
* @param {Object} callerAjaxParameters Do not specify a parameter unless you really
* really want to give jQuery some extra parameters
* @returns {promise} - a jQuery promise object that is resolved or rejected with the api object.
*/
post: function(callerAjaxParameters) {
++Morebits.wiki.numberOfActionsLeft;
var queryString = $.map(this.query, function(val, i) {
if (Array.isArray(val)) {
return encodeURIComponent(i) + '=' + val.map(encodeURIComponent).join('|');
} else if (val !== undefined) {
return encodeURIComponent(i) + '=' + encodeURIComponent(val);
}
}).join('&').replace(/^(.*?)(\btoken=[^&]*)&(.*)/, '$1$3&$2');
// token should always be the last item in the query string (bug TW-B-0013)
var ajaxparams = $.extend({}, {
context: this,
type: 'POST',
url: mw.util.wikiScript('api'),
data: queryString,
dataType: 'xml',
headers: {
'Api-User-Agent': morebitsWikiApiUserAgent
}
}, callerAjaxParameters);
return $.ajax(ajaxparams).then(
function onAPIsuccess(response, statusText) {
this.statusText = statusText;
this.response = this.responseXML = response;
if (this.query.format === 'json') {
this.errorCode = response.error && response.error.code;
this.errorText = response.error && response.error.info;
} else {
this.errorCode = $(response).find('error').attr('code');
this.errorText = $(response).find('error').attr('info');
}
if (typeof this.errorCode === 'string') {
// the API didn't like what we told it, e.g., bad edit token or an error creating a page
return this.returnError();
}
// invoke success callback if one was supplied
if (this.onSuccess) {
// set the callback context to this.parent for new code and supply the API object
// as the first argument to the callback (for legacy code)
this.onSuccess.call(this.parent, this);
} else {
this.statelem.info('انجام شد'); // localized
}
Morebits.wiki.actionCompleted();
return $.Deferred().resolveWith(this.parent, [this]);
},
// only network and server errors reach here - complaints from the API itself are caught in success()
function onAPIfailure(jqXHR, statusText, errorThrown) {
this.statusText = statusText;
this.errorThrown = errorThrown; // frequently undefined
this.errorText = statusText + ' "' + jqXHR.statusText + '" occurred while contacting the API.';
return this.returnError();
}
);
},
returnError: function() {
if (this.errorCode === 'badtoken') {
// TODO: automatically retry after getting a new token
this.statelem.error('Invalid token. Refresh the page and try again');
} else {
this.statelem.error(this.errorText);
}
// invoke failure callback if one was supplied
if (this.onError) {
// set the callback context to this.parent for new code and supply the API object
// as the first argument to the callback for legacy code
this.onError.call(this.parent, this);
}
// don't complete the action so that the error remains displayed
return $.Deferred().rejectWith(this.parent, [this]);
},
getStatusElement: function() {
return this.statelem;
},
getErrorCode: function() {
return this.errorCode;
},
getErrorText: function() {
return this.errorText;
},
getXML: function() { // retained for backwards compatibility, use getResponse() instead
return this.responseXML;
},
getResponse: function() {
return this.response;
}
};
// Custom user agent header, used by WMF for server-side logging
// See https://lists.wikimedia.org/pipermail/mediawiki-api-announce/2014-November/000075.html
var morebitsWikiApiUserAgent = 'morebits.js/2.0 ([[w:WT:TW]])';
/**
* Sets the custom user agent header
* @param {string} ua User agent
*/
Morebits.wiki.api.setApiUserAgent = function(ua) {
morebitsWikiApiUserAgent = (ua ? ua + ' ' : '') + 'morebits.js/2.0 ([[w:WT:TW]])';
};
/**
* **************** Morebits.wiki.page ****************
* Uses the MediaWiki API to load a page and optionally edit it, move it, etc.
*
* Callers are not permitted to directly access the properties of this class!
* All property access is through the appropriate get___() or set___() method.
*
* Callers should set Morebits.wiki.actionCompleted.notice and Morebits.wiki.actionCompleted.redirect
* before the first call to Morebits.wiki.page.load().
*
* Each of the callback functions takes one parameter, which is a
* reference to the Morebits.wiki.page object that registered the callback.
* Callback functions may invoke any Morebits.wiki.page prototype method using this reference.
*
*
* HIGHLIGHTS:
*
* Constructor: Morebits.wiki.page(pageName, currentAction)
* pageName - the name of the page, prefixed by the namespace (if any)
* (for the current page, use mw.config.get('wgPageName'))
* currentAction - a string describing the action about to be undertaken (optional)
*
* onSuccess and onFailure are callback functions called when the operation is a success or failure
* if enclosed in [brackets], it indicates that it is optional
*
* load(onSuccess, [onFailure]): Loads the text for the page
*
* getPageText(): returns a string containing the text of the page after a successful load()
*
* save([onSuccess], [onFailure]): Saves the text set via setPageText() for the page.
* Must be preceded by calling load().
* Warning: Calling save() can result in additional calls to the previous load() callbacks to
* recover from edit conflicts!
* In this case, callers must make the same edit to the new pageText and reinvoke save().
* This behavior can be disabled with setMaxConflictRetries(0).
*
* append([onSuccess], [onFailure]): Adds the text provided via setAppendText() to the end of
* the page. Does not require calling load() first.
*
* prepend([onSuccess], [onFailure]): Adds the text provided via setPrependText() to the start
* of the page. Does not require calling load() first.
*
* move([onSuccess], [onFailure]): Moves a page to another title
*
* patrol(): Patrols a page; ignores errors
*
* triage(): Marks page as reviewed using PageTriage, which implies patrolled; ignores most errors
*
* deletePage([onSuccess], [onFailure]): Deletes a page (for admins only)
*
* undeletePage([onSuccess], [onFailure]): Undeletes a page (for admins only)
*
* protect([onSuccess], [onFailure]): Protects a page
*
* getPageName(): returns a string containing the name of the loaded page, including the namespace
*
* setPageText(pageText) sets the updated page text that will be saved when save() is called
*
* setAppendText(appendText) sets the text that will be appended to the page when append() is called
*
* setPrependText(prependText) sets the text that will be prepended to the page when prepend() is called
*
* setCallbackParameters(callbackParameters)
* callbackParameters - an object for use in a callback function
*
* getCallbackParameters(): returns the object previous set by setCallbackParameters()
*
* Callback notes: callbackParameters is for use by the caller only. The parameters
* allow a caller to pass the proper context into its callback function.
* Callers must ensure that any changes to the callbackParameters object
* within a load() callback still permit a proper re-entry into the
* load() callback if an edit conflict is detected upon calling save().
*
* getStatusElement(): returns the Status element created by the constructor
*
* exists(): returns true if the page existed on the wiki when it was last loaded
*
* getCurrentID(): returns a string containing the current revision ID of the page
*
* lookupCreation(onSuccess): Retrieves the username and timestamp of page creation
* onSuccess - callback function which is called when the username and timestamp
* are found within the callback.
* The username can be retrieved using the getCreator() function;
* the timestamp can be retrieved using the getCreationTimestamp() function
*
* getCreator(): returns the user who created the page following lookupCreation()
*
* getCreationTimestamp(): returns an ISOString timestamp of page creation following lookupCreation()
*
*/
/**
* Call sequence for common operations (optional final user callbacks not shown):
*
* Edit current contents of a page (no edit conflict):
* .load(userTextEditCallback) -> ctx.loadApi.post() -> ctx.loadApi.post.success() ->
* ctx.fnLoadSuccess() -> userTextEditCallback() -> .save() ->
* ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()
*
* Edit current contents of a page (with edit conflict):
* .load(userTextEditCallback) -> ctx.loadApi.post() -> ctx.loadApi.post.success() ->
* ctx.fnLoadSuccess() -> userTextEditCallback() -> .save() ->
* ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveError() ->
* ctx.loadApi.post() -> ctx.loadApi.post.success() ->
* ctx.fnLoadSuccess() -> userTextEditCallback() -> .save() ->
* ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()
*
* Append to a page (similar for prepend):
* .append() -> ctx.loadApi.post() -> ctx.loadApi.post.success() ->
* ctx.fnLoadSuccess() -> ctx.fnAutoSave() -> .save() ->
* ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()
*
* Notes:
* 1. All functions following Morebits.wiki.api.post() are invoked asynchronously
* from the jQuery AJAX library.
* 2. The sequence for append/prepend could be slightly shortened, but it would require
* significant duplication of code for little benefit.
*/
/**
* @constructor
* @param {string} pageName The name of the page, prefixed by the namespace (if any)
* For the current page, use mw.config.get('wgPageName')
* @param {string} [currentAction] A string describing the action about to be undertaken (optional)
*/
Morebits.wiki.page = function(pageName, currentAction) {
if (!currentAction) {
currentAction = 'باز کردن صفحهٔ «' + pageName + '»'; // localized
}
/**
* Private context variables
*
* This context is not visible to the outside, thus all the data here
* must be accessed via getter and setter functions.
*/
var ctx = {
// backing fields for public properties
pageName: pageName,
pageExists: false,
editSummary: null,
callbackParameters: null,
statusElement: new Morebits.status(currentAction),
// - edit
pageText: null,
editMode: 'all', // save() replaces entire contents of the page by default
appendText: null, // can't reuse pageText for this because pageText is needed to follow a redirect
prependText: null, // can't reuse pageText for this because pageText is needed to follow a redirect
createOption: null,
minorEdit: false,
botEdit: false,
pageSection: null,
maxConflictRetries: 2,
maxRetries: 2,
followRedirect: false,
watchlistOption: 'nochange',
creator: null,
timestamp: null,
// - revert
revertOldID: null,
// - move
moveDestination: null,
moveTalkPage: false,
moveSubpages: false,
moveSuppressRedirect: false,
// - protect
protectEdit: null,
protectMove: null,
protectCreate: null,
protectCascade: false,
// - creation lookup
lookupNonRedirectCreator: false,
// - stabilize (FlaggedRevs)
flaggedRevs: null,
// internal status
pageLoaded: false,
csrfToken: null,
loadTime: null,
lastEditTime: null,
pageID: null,
revertCurID: null,
revertUser: null,
fullyProtected: false,
suppressProtectWarning: false,
conflictRetries: 0,
retries: 0,
// callbacks
onLoadSuccess: null,
onLoadFailure: null,
onSaveSuccess: null,
onSaveFailure: null,
onLookupCreationSuccess: null,
onMoveSuccess: null,
onMoveFailure: null,
onDeleteSuccess: null,
onDeleteFailure: null,
onUndeleteSuccess: null,
onUndeleteFailure: null,
onProtectSuccess: null,
onProtectFailure: null,
onStabilizeSuccess: null,
onStabilizeFailure: null,
// internal objects
loadQuery: null,
loadApi: null,
saveApi: null,
lookupCreationApi: null,
moveApi: null,
moveProcessApi: null,
patrolApi: null,
patrolProcessApi: null,
triageApi: null,
triageProcessApi: null,
deleteApi: null,
deleteProcessApi: null,
undeleteApi: null,
undeleteProcessApi: null,
protectApi: null,
protectProcessApi: null,
stabilizeApi: null,
stabilizeProcessApi: null
};
var emptyFunction = function() { };
/**
* Loads the text for the page
* @param {Function} onSuccess - callback function which is called when the load has succeeded
* @param {Function} [onFailure] - callback function which is called when the load fails (optional)
*/
this.load = function(onSuccess, onFailure) {
ctx.onLoadSuccess = onSuccess;
ctx.onLoadFailure = onFailure || emptyFunction;
// Need to be able to do something after the page loads
if (!onSuccess) {
ctx.statusElement.error('Internal error: no onSuccess callback provided to load()!');
ctx.onLoadFailure(this);
return;
}
ctx.loadQuery = {
action: 'query',
prop: 'info|revisions',
curtimestamp: '',
meta: 'tokens',
type: 'csrf',
titles: ctx.pageName
// don't need rvlimit=1 because we don't need rvstartid here and only one actual rev is returned by default
};
if (ctx.editMode === 'all') {
ctx.loadQuery.rvprop = 'content|timestamp'; // get the page content at the same time, if needed
} else if (ctx.editMode === 'revert') {
ctx.loadQuery.rvprop = 'timestamp';
ctx.loadQuery.rvlimit = 1;
ctx.loadQuery.rvstartid = ctx.revertOldID;
}
if (ctx.followRedirect) {
ctx.loadQuery.redirects = ''; // follow all redirects
}
if (typeof ctx.pageSection === 'number') {
ctx.loadQuery.rvsection = ctx.pageSection;
}
if (Morebits.userIsSysop) {
ctx.loadQuery.inprop = 'protection';
}
ctx.loadApi = new Morebits.wiki.api('Retrieving page...', ctx.loadQuery, fnLoadSuccess, ctx.statusElement, ctx.onLoadFailure);
ctx.loadApi.setParent(this);
ctx.loadApi.post();
};
/**
* Saves the text for the page to Wikipedia
* Must be preceded by successfully calling load().
*
* Warning: Calling save() can result in additional calls to the previous load() callbacks
* to recover from edit conflicts!
* In this case, callers must make the same edit to the new pageText and reinvoke save().
* This behavior can be disabled with setMaxConflictRetries(0).
* @param {Function} [onSuccess] - callback function which is called when the save has
* succeeded (optional)
* @param {Function} [onFailure] - callback function which is called when the save fails
* (optional)
*/
this.save = function(onSuccess, onFailure) {
ctx.onSaveSuccess = onSuccess;
ctx.onSaveFailure = onFailure || emptyFunction;
// are we getting our editing token from mw.user.tokens?
var canUseMwUserToken = fnCanUseMwUserToken('edit');
if (!ctx.pageLoaded && !canUseMwUserToken) {
ctx.statusElement.error('Internal error: attempt to save a page that has not been loaded!');
ctx.onSaveFailure(this);
return;
}
if (!ctx.editSummary) {
ctx.statusElement.error('Internal error: edit summary not set before save!');
ctx.onSaveFailure(this);
return;
}
// shouldn't happen if canUseMwUserToken === true
if (ctx.fullyProtected && !ctx.suppressProtectWarning &&
!confirm('You are about to make an edit to the fully protected page "' + ctx.pageName +
(ctx.fullyProtected === 'infinity' ? '" (protected indefinitely)' : '" (protection expiring ' + new Morebits.date(ctx.fullyProtected).calendar('utc') + ' (UTC))') +
'. \n\nClick OK to proceed with the edit, or Cancel to skip this edit.')) {
ctx.statusElement.error('Edit to fully protected page was aborted.');
ctx.onSaveFailure(this);
return;
}
ctx.retries = 0;
var query = {
action: 'edit',
title: ctx.pageName,
summary: ctx.editSummary,
token: canUseMwUserToken ? mw.user.tokens.get('csrfToken') : ctx.csrfToken,
watchlist: ctx.watchlistOption
};
if (typeof ctx.pageSection === 'number') {
query.section = ctx.pageSection;
}
// Set minor edit attribute. If these parameters are present with any value, it is interpreted as true
if (ctx.minorEdit) {
query.minor = true;
} else {
query.notminor = true; // force Twinkle config to override user preference setting for "all edits are minor"
}
// Set bot edit attribute. If this paramter is present with any value, it is interpreted as true
if (ctx.botEdit) {
query.bot = true;
}
switch (ctx.editMode) {
case 'append':
query.appendtext = ctx.appendText; // use mode to append to current page contents
break;
case 'prepend':
query.prependtext = ctx.prependText; // use mode to prepend to current page contents
break;
case 'revert':
query.undo = ctx.revertCurID;
query.undoafter = ctx.revertOldID;
if (ctx.lastEditTime) {
query.basetimestamp = ctx.lastEditTime; // check that page hasn't been edited since it was loaded
}
query.starttimestamp = ctx.loadTime; // check that page hasn't been deleted since it was loaded (don't recreate bad stuff)
break;
default: // 'all'
query.text = ctx.pageText; // replace entire contents of the page
if (ctx.lastEditTime) {
query.basetimestamp = ctx.lastEditTime; // check that page hasn't been edited since it was loaded
}
query.starttimestamp = ctx.loadTime; // check that page hasn't been deleted since it was loaded (don't recreate bad stuff)
break;
}
if (['recreate', 'createonly', 'nocreate'].indexOf(ctx.createOption) !== -1) {
query[ctx.createOption] = '';
}
if (canUseMwUserToken && ctx.followRedirect) {
query.redirect = true;
}
ctx.saveApi = new Morebits.wiki.api('Saving page...', query, fnSaveSuccess, ctx.statusElement, fnSaveError);
ctx.saveApi.setParent(this);
ctx.saveApi.post();
};
/**
* Adds the text provided via setAppendText() to the end of the page.
* Does not require calling load() first.
* @param {Function} [onSuccess] - callback function which is called when the method has succeeded (optional)
* @param {Function} [onFailure] - callback function which is called when the method fails (optional)
*/
this.append = function(onSuccess, onFailure) {
ctx.editMode = 'append';
if (fnCanUseMwUserToken('edit')) {
this.save(onSuccess, onFailure);
} else {
ctx.onSaveSuccess = onSuccess;
ctx.onSaveFailure = onFailure || emptyFunction;
this.load(fnAutoSave, ctx.onSaveFailure);
}
};
/**
* Adds the text provided via setPrependText() to the start of the page.
* Does not require calling load() first.
* @param {Function} [onSuccess] - callback function which is called when the method has succeeded (optional)
* @param {Function} [onFailure] - callback function which is called when the method fails (optional)
*/
this.prepend = function(onSuccess, onFailure) {
ctx.editMode = 'prepend';
if (fnCanUseMwUserToken('edit')) {
this.save(onSuccess, onFailure);
} else {
ctx.onSaveSuccess = onSuccess;
ctx.onSaveFailure = onFailure || emptyFunction;
this.load(fnAutoSave, ctx.onSaveFailure);
}
};
/** @returns {string} string containing the name of the loaded page, including the namespace */
this.getPageName = function() {
return ctx.pageName;
};
/** @returns {string} string containing the text of the page after a successful load() */
this.getPageText = function() {
return ctx.pageText;
};
/** @param {string} pageText - updated page text that will be saved when save() is called */
this.setPageText = function(pageText) {
ctx.editMode = 'all';
ctx.pageText = pageText;
};
/** @param {string} appendText - text that will be appended to the page when append() is called */
this.setAppendText = function(appendText) {
ctx.editMode = 'append';
ctx.appendText = appendText;
};
/** @param {string} prependText - text that will be prepended to the page when prepend() is called */
this.setPrependText = function(prependText) {
ctx.editMode = 'prepend';
ctx.prependText = prependText;
};
// Edit-related setter methods:
/** @param {string} summary - text of the edit summary that will be used when save() is called */
this.setEditSummary = function(summary) {
ctx.editSummary = summary;
};
/**
* @param {string} createOption - can take the following four values:
* `recreate` - create the page if it does not exist, or edit it if it exists.
* `createonly` - create the page if it does not exist, but return an error if it
* already exists.
* `nocreate` - don't create the page, only edit it if it already exists.
* null - create the page if it does not exist, unless it was deleted in the moment
* between loading the page and saving the edit (default)
*
*/
this.setCreateOption = function(createOption) {
ctx.createOption = createOption;
};
/** @param {boolean} minorEdit - set true to mark the edit as a minor edit. */
this.setMinorEdit = function(minorEdit) {
ctx.minorEdit = minorEdit;
};
/** @param {boolean} botEdit - set true to mark the edit as a bot edit */
this.setBotEdit = function(botEdit) {
ctx.botEdit = botEdit;
};
/**
* @param {number} pageSection - integer specifying the section number to load or save.
* If specified as `null`, the entire page will be retrieved.
*/
this.setPageSection = function(pageSection) {
ctx.pageSection = pageSection;
};
/**
* @param {number} maxConflictRetries - number of retries for save errors involving an edit conflict or
* loss of token. Default: 2
*/
this.setMaxConflictRetries = function(maxConflictRetries) {
ctx.maxConflictRetries = maxConflictRetries;
};
/**
* @param {number} maxRetries - number of retries for save errors not involving an edit conflict or
* loss of token. Default: 2
*/
this.setMaxRetries = function(maxRetries) {
ctx.maxRetries = maxRetries;
};
/**
* @param {boolean} watchlistOption
* True - page will be added to the user's watchlist when save() is called
* False - watchlist status of the page will not be changed (default)
*/
this.setWatchlist = function(watchlistOption) {
if (watchlistOption) {
ctx.watchlistOption = 'watch';
} else {
ctx.watchlistOption = 'nochange';
}
};
/**
* @param {boolean} watchlistOption
* True - page watchlist status will be set based on the user's
* preference settings when save() is called.
* False - watchlist status of the page will not be changed (default)
*
* Watchlist notes:
* 1. The MediaWiki API value of 'unwatch', which explicitly removes the page from the
* user's watchlist, is not used.
* 2. If both setWatchlist() and setWatchlistFromPreferences() are called,
* the last call takes priority.
* 3. Twinkle modules should use the appropriate preference to set the watchlist options.
* 4. Most Twinkle modules use setWatchlist().
* setWatchlistFromPreferences() is only needed for the few Twinkle watchlist preferences
* that accept a string value of 'default'.
*/
this.setWatchlistFromPreferences = function(watchlistOption) {
if (watchlistOption) {
ctx.watchlistOption = 'preferences';
} else {
ctx.watchlistOption = 'nochange';
}
};
/**
* @param {boolean} followRedirect
* true - a maximum of one redirect will be followed.
* In the event of a redirect, a message is displayed to the user and
* the redirect target can be retrieved with getPageName().
* false - the requested pageName will be used without regard to any redirect (default).
*/
this.setFollowRedirect = function(followRedirect) {
if (ctx.pageLoaded) {
ctx.statusElement.error('Internal error: cannot change redirect setting after the page has been loaded!');
return;
}
ctx.followRedirect = followRedirect;
};
// lookup-creation setter function
/**
* @param {boolean} flag - if set true, the author and timestamp of the first non-redirect
* version of the page is retrieved.
*
* Warning:
* 1. If there are no revisions among the first 50 that are non-redirects, or if there are
* less 50 revisions and all are redirects, the original creation is retrived.
* 2. Revisions that the user is not privileged to access (revdeled/suppressed) will be treated
* as non-redirects.
* 3. Must not be used when the page has a non-wikitext contentmodel
* such as Modulespace Lua or user JavaScript/CSS
*/
this.setLookupNonRedirectCreator = function(flag) {
ctx.lookupNonRedirectCreator = flag;
};
// Move-related setter functions
/** @param {string} destination */
this.setMoveDestination = function(destination) {
ctx.moveDestination = destination;
};
/** @param {boolean} flag */
this.setMoveTalkPage = function(flag) {
ctx.moveTalkPage = !!flag;
};
/** @param {boolean} flag */
this.setMoveSubpages = function(flag) {
ctx.moveSubpages = !!flag;
};
/** @param {boolean} flag */
this.setMoveSuppressRedirect = function(flag) {
ctx.moveSuppressRedirect = !!flag;
};
// Protect-related setter functions
this.setEditProtection = function(level, expiry) {
ctx.protectEdit = { level: level, expiry: expiry };
};
this.setMoveProtection = function(level, expiry) {
ctx.protectMove = { level: level, expiry: expiry };
};
this.setCreateProtection = function(level, expiry) {
ctx.protectCreate = { level: level, expiry: expiry };
};
this.setCascadingProtection = function(flag) {
ctx.protectCascade = !!flag;
};
this.suppressProtectWarning = function() {
ctx.suppressProtectWarning = true;
};
// Revert-related getters/setters:
this.setOldID = function(oldID) {
ctx.revertOldID = oldID;
};
/** @returns {string} string containing the current revision ID of the page */
this.getCurrentID = function() {
return ctx.revertCurID;
};
/** @returns {string} last editor of the page */
this.getRevisionUser = function() {
return ctx.revertUser;
};
/** @returns {string} ISO 8601 timestamp at which the page was last edited. */
this.getLastEditTime = function() {
return ctx.lastEditTime;
};
// Miscellaneous getters/setters:
/**
* `callbackParameters` - an object for use in a callback function
*
* Callback notes: callbackParameters is for use by the caller only. The parameters
* allow a caller to pass the proper context into its callback function.
* Callers must ensure that any changes to the callbackParameters object
* within a load() callback still permit a proper re-entry into the
* load() callback if an edit conflict is detected upon calling save().
*/
this.setCallbackParameters = function(callbackParameters) {
ctx.callbackParameters = callbackParameters;
};
/**
* @returns the object previous set by setCallbackParameters()
*/
this.getCallbackParameters = function() {
return ctx.callbackParameters;
};
/**
* @returns {Morebits.status} Status element created by the constructor
*/
this.getStatusElement = function() {
return ctx.statusElement;
};
/**
* @param {string} level The right required for edits not to require
* review. Possible options: none, autoconfirmed, review (not on enWiki).
* @param {string} expiry
*/
this.setFlaggedRevs = function(level, expiry) {
ctx.flaggedRevs = { level: level, expiry: expiry };
};
/**
* @returns {boolean} true if the page existed on the wiki when it was last loaded
*/
this.exists = function() {
return ctx.pageExists;
};
/**
* @returns {string} Page ID of the page loaded. 0 if the page doesn't
* exist.
*/
this.getPageID = function() {
return ctx.pageID;
};
/**
* @returns {string} ISO 8601 timestamp at which the page was last loaded
*/
this.getLoadTime = function() {
return ctx.loadTime;
};
/**
* @returns {string} the user who created the page following lookupCreation()
*/
this.getCreator = function() {
return ctx.creator;
};
/**
* @returns {string} the ISOString timestamp of page creation following lookupCreation()
*/
this.getCreationTimestamp = function() {
return ctx.timestamp;
};
/**
* Retrieves the username of the user who created the page as well as
* the timestamp of creation
* @param {Function} onSuccess - callback function (required) which is
* called when the username and timestamp are found within the callback.
* The username can be retrieved using the getCreator() function;
* the timestamp can be retrieved using the getCreationTimestamp() function
* Prior to June 2019 known as lookupCreator
*/
this.lookupCreation = function(onSuccess) {
if (!onSuccess) {
ctx.statusElement.error('Internal error: no onSuccess callback provided to lookupCreation()!');
return;
}
ctx.onLookupCreationSuccess = onSuccess;
var query = {
'action': 'query',
'prop': 'revisions',
'titles': ctx.pageName,
'rvlimit': 1,
'rvprop': 'user|timestamp',
'rvdir': 'newer'
};
// Only the wikitext content model can reliably handle
// rvsection, others return an error when paired with the
// content rvprop. Relatedly, non-wikitext models don't
// understand the #REDIRECT concept, so we shouldn't attempt
// the redirect resolution in fnLookupCreationSuccess
if (ctx.lookupNonRedirectCreator) {
query.rvsection = 0;
query.rvprop += '|content';
}
if (ctx.followRedirect) {
query.redirects = ''; // follow all redirects
}
ctx.lookupCreationApi = new Morebits.wiki.api('Retrieving page creation information', query, fnLookupCreationSuccess, ctx.statusElement);
ctx.lookupCreationApi.setParent(this);
ctx.lookupCreationApi.post();
};
/**
* Reverts a page to revertOldID
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.revert = function(onSuccess, onFailure) {
ctx.onSaveSuccess = onSuccess;
ctx.onSaveFailure = onFailure || emptyFunction;
if (!ctx.revertOldID) {
ctx.statusElement.error('Internal error: revision ID to revert to was not set before revert!');
ctx.onSaveFailure(this);
return;
}
ctx.editMode = 'revert';
this.load(fnAutoSave, ctx.onSaveFailure);
};
/**
* Moves a page to another title
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.move = function(onSuccess, onFailure) {
ctx.onMoveSuccess = onSuccess;
ctx.onMoveFailure = onFailure || emptyFunction;
if (!ctx.editSummary) {
ctx.statusElement.error('Internal error: move reason not set before move (use setEditSummary function)!');
ctx.onMoveFailure(this);
return;
}
if (!ctx.moveDestination) {
ctx.statusElement.error('Internal error: destination page name was not set before move!');
ctx.onMoveFailure(this);
return;
}
if (fnCanUseMwUserToken('move')) {
fnProcessMove.call(this, this);
} else {
var query = fnNeedTokenInfoQuery('move');
ctx.moveApi = new Morebits.wiki.api('retrieving token...', query, fnProcessMove, ctx.statusElement, ctx.onMoveFailure);
ctx.moveApi.setParent(this);
ctx.moveApi.post();
}
};
/**
* Marks the page as patrolled, using rcid (if available) or revid
*
* Patrolling as such doesn't need to rely on loading the page in
* question; simply passing a revid to the API is sufficient, so in
* those cases just using Morebits.wiki.api is probably preferable.
*
* No error handling since we don't actually care about the errors
*/
this.patrol = function() {
if (!Morebits.userIsSysop && !Morebits.userIsInGroup('patroller')) {
return;
}
// If a link is present, don't need to check if it's patrolled
if ($('.patrollink').length) {
var patrolhref = $('.patrollink a').attr('href');
ctx.rcid = mw.util.getParamValue('rcid', patrolhref);
fnProcessPatrol(this, this);
} else {
var patrolQuery = {
action: 'query',
prop: 'info',
meta: 'tokens',
type: 'patrol', // as long as we're querying, might as well get a token
list: 'recentchanges', // check if the page is unpatrolled
titles: ctx.pageName,
rcprop: 'patrolled',
rctitle: ctx.pageName,
rclimit: 1
};
ctx.patrolApi = new Morebits.wiki.api('retrieving token...', patrolQuery, fnProcessPatrol);
ctx.patrolApi.setParent(this);
ctx.patrolApi.post();
}
};
/**
* Marks the page as reviewed by the PageTriage extension
* https://www.mediawiki.org/wiki/Extension:PageTriage
*
* Referred to as "review" on-wiki
*
* Will, by it's nature, mark as patrolled as well. Falls back to
* patrolling if not in an appropriate namespace.
*
* Doesn't inherently rely on loading the page in question; simply
* passing a pageid to the API is sufficient, so in those cases just
* using Morebits.wiki.api is probably preferable.
*
* No error handling since we don't actually care about the errors
*/
this.triage = function() {
if (mw.config.get('pageTriageNamespaces') === null ) {
return; // localized [bypassed]
}
// Fall back to patrol if not a valid triage namespace
if (mw.config.get('pageTriageNamespaces').indexOf(mw.config.get('wgNamespaceNumber')) === -1) {
this.patrol();
} else {
if (!Morebits.userIsSysop && !Morebits.userIsInGroup('patroller')) {
return;
}
// If on the page in question, don't need to query for page ID
if (new mw.Title(Morebits.pageNameNorm).getPrefixedText() === new mw.Title(ctx.pageName).getPrefixedText()) {
ctx.pageID = mw.config.get('wgArticleId');
fnProcessTriage(this, this);
} else {
var query = fnNeedTokenInfoQuery('triage');
ctx.triageApi = new Morebits.wiki.api('retrieving token...', query, fnProcessTriage);
ctx.triageApi.setParent(this);
ctx.triageApi.post();
}
}
};
// |delete| is a reserved word in some flavours of JS
/**
* Deletes a page (for admins only)
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.deletePage = function(onSuccess, onFailure) {
ctx.onDeleteSuccess = onSuccess;
ctx.onDeleteFailure = onFailure || emptyFunction;
// if a non-admin tries to do this, don't bother
if (!Morebits.userIsSysop) {
ctx.statusElement.error('Cannot delete page: only admins can do that');
ctx.onDeleteFailure(this);
return;
}
if (!ctx.editSummary) {
ctx.statusElement.error('Internal error: delete reason not set before delete (use setEditSummary function)!');
ctx.onDeleteFailure(this);
return;
}
if (fnCanUseMwUserToken('delete')) {
fnProcessDelete.call(this, this);
} else {
var query = fnNeedTokenInfoQuery('delete');
ctx.deleteApi = new Morebits.wiki.api('retrieving token...', query, fnProcessDelete, ctx.statusElement, ctx.onDeleteFailure);
ctx.deleteApi.setParent(this);
ctx.deleteApi.post();
}
};
/**
* Undeletes a page (for admins only)
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.undeletePage = function(onSuccess, onFailure) {
ctx.onUndeleteSuccess = onSuccess;
ctx.onUndeleteFailure = onFailure || emptyFunction;
// if a non-admin tries to do this, don't bother
if (!Morebits.userIsSysop) {
ctx.statusElement.error('Cannot undelete page: only admins can do that');
ctx.onUndeleteFailure(this);
return;
}
if (!ctx.editSummary) {
ctx.statusElement.error('Internal error: undelete reason not set before undelete (use setEditSummary function)!');
ctx.onUndeleteFailure(this);
return;
}
if (fnCanUseMwUserToken('undelete')) {
fnProcessUndelete.call(this, this);
} else {
var query = fnNeedTokenInfoQuery('undelete');
ctx.undeleteApi = new Morebits.wiki.api('retrieving token...', query, fnProcessUndelete, ctx.statusElement, ctx.onUndeleteFailure);
ctx.undeleteApi.setParent(this);
ctx.undeleteApi.post();
}
};
/**
* Protects a page (for admins only)
* @param {Function} [onSuccess] - callback function to run on success (optional)
* @param {Function} [onFailure] - callback function to run on failure (optional)
*/
this.protect = function(onSuccess, onFailure) {
ctx.onProtectSuccess = onSuccess;
ctx.onProtectFailure = onFailure || emptyFunction;
// if a non-admin tries to do this, don't bother
if (!Morebits.userIsSysop) {
ctx.statusElement.error('Cannot protect page: only admins can do that');
ctx.onProtectFailure(this);
return;
}
if (!ctx.protectEdit && !ctx.protectMove && !ctx.protectCreate) {
ctx.statusElement.error('Internal error: you must set edit and/or move and/or create protection before calling protect()!');
ctx.onProtectFailure(this);
return;
}
if (!ctx.editSummary) {
ctx.statusElement.error('Internal error: protection reason not set before protect (use setEditSummary function)!');
ctx.onProtectFailure(this);
return;
}
// because of the way MW API interprets protection levels
// (absolute, not differential), we always need to request
// protection levels from the server
var query = fnNeedTokenInfoQuery('protect');
ctx.protectApi = new Morebits.wiki.api('retrieving token...', query, fnProcessProtect, ctx.statusElement, ctx.onProtectFailure);
ctx.protectApi.setParent(this);
ctx.protectApi.post();
};
/**
* Apply FlaggedRevs protection-style settings
* only works where $wgFlaggedRevsProtection = true (i.e. where FlaggedRevs
* settings appear on the wiki's "protect" tab)
* @param {function} [onSuccess]
* @param {function} [onFailure]
*/
this.stabilize = function(onSuccess, onFailure) {
ctx.onStabilizeSuccess = onSuccess;
ctx.onStabilizeFailure = onFailure || emptyFunction;
// if a non-admin tries to do this, don't bother
if (!Morebits.userIsSysop) {
ctx.statusElement.error('Cannot apply FlaggedRevs settings: only admins can do that');
ctx.onStabilizeFailure(this);
return;
}
if (!ctx.flaggedRevs) {
ctx.statusElement.error('Internal error: you must set flaggedRevs before calling stabilize()!');
ctx.onStabilizeFailure(this);
return;
}
if (!ctx.editSummary) {
ctx.statusElement.error('Internal error: reason not set before calling stabilize() (use setEditSummary function)!');
ctx.onStabilizeFailure(this);
return;
}
if (fnCanUseMwUserToken('stabilize')) {
fnProcessStabilize.call(this, this);
} else {
var query = fnNeedTokenInfoQuery('stabilize');
ctx.stabilizeApi = new Morebits.wiki.api('retrieving token...', query, fnProcessStabilize, ctx.statusElement, ctx.onStabilizeFailure);
ctx.stabilizeApi.setParent(this);
ctx.stabilizeApi.post();
}
};
/*
* Private member functions
* These are not exposed outside
*/
/**
* Determines whether we can save an API call by using the csrf token sent with the page
* HTML, or whether we need to ask the server for more info (e.g. protection expiry).
*
* Only applicable for csrf token actions, e.g. not patrol
*
* Currently used for append, prepend, deletePage, undeletePage, move,
* and stabilize. Can't use for protect since it always needs to
* request protection status.
*
* @param {string} [action=edit] The action being undertaken, e.g.
* "edit" or "delete". In practice, only "edit" or "notedit" matters.
* @returns {boolean}
*/
var fnCanUseMwUserToken = function(action) {
action = typeof action !== 'undefined' ? action : 'edit'; // IE doesn't support default parameters
// API-based redirect resolution only works for action=query and
// action=edit in append/prepend modes (and section=new, but we don't
// really support that)
if (ctx.followRedirect && (action !== 'edit' ||
(ctx.editMode !== 'append' && ctx.editMode !== 'prepend'))) {
return false;
}
// do we need to fetch the edit protection expiry?
if (Morebits.userIsSysop && !ctx.suppressProtectWarning) {
if (new mw.Title(Morebits.pageNameNorm).getPrefixedText() === new mw.Title(ctx.pageName).getPrefixedText()) {
return false;
}
// wgRestrictionEdit is null on non-existent pages,
// so this neatly handles nonexistent pages
var editRestriction = mw.config.get('wgRestrictionEdit');
if (!editRestriction || editRestriction.indexOf('sysop') !== -1) {
return false;
}
}
return !!mw.user.tokens.get('csrfToken');
};
/**
* When functions can't use fnCanUseMwUserToken or require checking
* protection, maintain the query in one place. Used for delete,
* undelete, protect, stabilize, and move (basically, just not load)
*
* @param {string} action The action being undertaken, e.g. "edit" or
* "delete"
*/
var fnNeedTokenInfoQuery = function(action) {
var query = {
action: 'query',
meta: 'tokens',
type: 'csrf',
titles: ctx.pageName
};
// Protection not checked for flagged-revs or non-sysop moves
if (action !== 'stabilize' && (action !== 'move' || Morebits.userIsSysop)) {
query.prop = 'info';
query.inprop = 'protection';
}
if (ctx.followRedirect && action !== 'undelete') {
query.redirects = ''; // follow all redirects
}
return query;
};
// callback from loadSuccess() for append() and prepend() threads
var fnAutoSave = function(pageobj) {
pageobj.save(ctx.onSaveSuccess, ctx.onSaveFailure);
};
// callback from loadApi.post()
var fnLoadSuccess = function() {
var xml = ctx.loadApi.getXML();
if (!fnCheckPageName(xml, ctx.onLoadFailure)) {
return; // abort
}
ctx.pageExists = $(xml).find('page').attr('missing') !== '';
if (ctx.pageExists) {
ctx.pageText = $(xml).find('rev').text();
ctx.pageID = $(xml).find('page').attr('pageid');
} else {
ctx.pageText = ''; // allow for concatenation, etc.
ctx.pageID = 0; // nonexistent in response, matches wgArticleId
}
ctx.csrfToken = $(xml).find('tokens').attr('csrftoken');
if (!ctx.csrfToken) {
ctx.statusElement.error('Failed to retrieve edit token.');
ctx.onLoadFailure(this);
return;
}
ctx.loadTime = $(xml).find('api').attr('curtimestamp');
if (!ctx.loadTime) {
ctx.statusElement.error('Failed to retrieve current timestamp.');
ctx.onLoadFailure(this);
return;
}
// extract protection info, to alert admins when they are about to edit a protected page
if (Morebits.userIsSysop) {
var editprot = $(xml).find('pr[type="edit"]');
if (editprot.length > 0 && editprot.attr('level') === 'sysop') {
ctx.fullyProtected = editprot.attr('expiry');
} else {
ctx.fullyProtected = false;
}
}
ctx.lastEditTime = $(xml).find('rev').attr('timestamp');
ctx.revertCurID = $(xml).find('page').attr('lastrevid');
if (ctx.editMode === 'revert') {
ctx.revertCurID = $(xml).find('rev').attr('revid');
if (!ctx.revertCurID) {
ctx.statusElement.error('Failed to retrieve current revision ID.');
ctx.onLoadFailure(this);
return;
}
ctx.revertUser = $(xml).find('rev').attr('user');
if (!ctx.revertUser) {
if ($(xml).find('rev').attr('userhidden') === '') { // username was RevDel'd or oversighted
ctx.revertUser = '<username hidden>';
} else {
ctx.statusElement.error('Failed to retrieve user who made the revision.');
ctx.onLoadFailure(this);
return;
}
}
// set revert edit summary
ctx.editSummary = '[[Help:Revert|Reverted]] to revision ' + ctx.revertOldID + ' by ' + ctx.revertUser + ': ' + ctx.editSummary;
}
ctx.pageLoaded = true;
// alert("Generate edit conflict now"); // for testing edit conflict recovery logic
ctx.onLoadSuccess(this); // invoke callback
};
// helper function to parse the page name returned from the API
var fnCheckPageName = function(xml, onFailure) {
if (!onFailure) {
onFailure = emptyFunction;
}
// check for invalid titles
if ($(xml).find('page').attr('invalid') === '') {
ctx.statusElement.error('The page title is invalid: ' + ctx.pageName);
onFailure(this);
return false; // abort
}
// retrieve actual title of the page after normalization and redirects
if ($(xml).find('page').attr('title')) {
var resolvedName = $(xml).find('page').attr('title');
// only notify user for redirects, not normalization
if ($(xml).find('redirects').length > 0) {
Morebits.status.info('Info', 'Redirected from ' + ctx.pageName + ' to ' + resolvedName);
}
ctx.pageName = resolvedName; // always update in case of normalization
} else {
// could be a circular redirect or other problem
ctx.statusElement.error('Could not resolve redirects for: ' + ctx.pageName);
onFailure(this);
// force error to stay on the screen
++Morebits.wiki.numberOfActionsLeft;
return false; // abort
}
return true; // all OK
};
// helper function to get a new token on encountering token errors in save, deletePage, and undeletePage
var fnGetToken = function() {
var tokenApi = new Morebits.wiki.api('Getting token', {
action: 'query',
meta: 'tokens'
});
return tokenApi.post().then(function(apiobj) {
return $(apiobj.responseXML).find('tokens').attr('csrftoken');
});
};
// callback from saveApi.post()
var fnSaveSuccess = function() {
ctx.editMode = 'all'; // cancel append/prepend/revert modes
var xml = ctx.saveApi.getXML();
// see if the API thinks we were successful
if ($(xml).find('edit').attr('result') === 'Success') {
// real success
// default on success action - display link for edited page
var link = document.createElement('a');
link.setAttribute('href', mw.util.getUrl(ctx.pageName));
link.appendChild(document.createTextNode(ctx.pageName));
ctx.statusElement.info(['کامل شد (', link, ')']); // localized
if (ctx.onSaveSuccess) {
ctx.onSaveSuccess(this); // invoke callback
}
return;
}
// errors here are only generated by extensions which hook APIEditBeforeSave within MediaWiki,
// which as of 1.34.0-wmf.23 (Sept 2019) should only encompass captcha messages
if ($(xml).find('captcha').length > 0) {
ctx.statusElement.error('Could not save the page because the wiki server wanted you to fill out a CAPTCHA.');
} else {
ctx.statusElement.error('Unknown error received from API while saving page');
}
// force error to stay on the screen
++Morebits.wiki.numberOfActionsLeft;
ctx.onSaveFailure(this);
};
// callback from saveApi.post()
var fnSaveError = function() {
var errorCode = ctx.saveApi.getErrorCode();
// check for edit conflict
if (errorCode === 'editconflict' && ctx.conflictRetries++ < ctx.maxConflictRetries) {
// edit conflicts can occur when the page needs to be purged from the server cache
var purgeQuery = {
action: 'purge',
titles: ctx.pageName // redirects are already resolved
};
var purgeApi = new Morebits.wiki.api('Edit conflict detected, purging server cache', purgeQuery, function() {
--Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds
ctx.statusElement.info('Edit conflict detected, reapplying edit');
if (fnCanUseMwUserToken('edit')) {
ctx.saveApi.post(); // necessarily append or prepend, so this should work as desired
} else {
ctx.loadApi.post(); // reload the page and reapply the edit
}
}, ctx.statusElement);
purgeApi.post();
// check for loss of edit token
} else if (errorCode === 'badtoken' && ctx.retries++ < ctx.maxRetries) {
ctx.statusElement.info('Edit token is invalid, retrying');
--Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds
fnGetToken().then(function(token) {
ctx.saveApi.query.token = token;
ctx.saveApi.post();
});
// check for network or server error
} else if (errorCode === 'undefined' && ctx.retries++ < ctx.maxRetries) {
// the error might be transient, so try again
ctx.statusElement.info('Save failed, retrying');
--Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds
ctx.saveApi.post(); // give it another go!
// hard error, give up
} else {
// non-admin attempting to edit a protected page - this gives a friendlier message than the default
if (errorCode === 'protectedpage') {
ctx.statusElement.error('Failed to save edit: Page is protected');
// check for absuefilter hits: disallowed or warning
} else if (errorCode.indexOf('abusefilter') === 0) {
var desc = $(ctx.saveApi.getXML()).find('abusefilter').attr('description');
if (errorCode === 'abusefilter-disallowed') {
ctx.statusElement.error('The edit was disallowed by the edit filter: "' + desc + '".');
} else if (errorCode === 'abusefilter-warning') {
ctx.statusElement.error([ 'A warning was returned by the edit filter: "', desc, '". If you wish to proceed with the edit, please carry it out again. This warning will not appear a second time.' ]);
// We should provide the user with a way to automatically retry the action if they so choose -
// I can't see how to do this without creating a UI dependency on Morebits.wiki.page though -- TTO
} else { // shouldn't happen but...
ctx.statusElement.error('The edit was disallowed by the edit filter.');
}
// check for blacklist hits
} else if (errorCode === 'spamblacklist') {
// .find('matches') returns an array in case multiple items are blacklisted, we only return the first
var spam = $(ctx.saveApi.getXML()).find('spamblacklist').find('matches').children()[0].textContent;
ctx.statusElement.error('Could not save the page because the URL ' + spam + ' is on the spam blacklist');
} else {
ctx.statusElement.error('Failed to save edit: ' + ctx.saveApi.getErrorText());
}
ctx.editMode = 'all'; // cancel append/prepend/revert modes
if (ctx.onSaveFailure) {
ctx.onSaveFailure(this); // invoke callback
}
}
};
var fnLookupCreationSuccess = function() {
var xml = ctx.lookupCreationApi.getXML();
if (!fnCheckPageName(xml)) {
return; // abort
}
if (!ctx.lookupNonRedirectCreator || !/^\s*#redirect/i.test($(xml).find('rev').text())) {
ctx.creator = $(xml).find('rev').attr('user');
if (!ctx.creator) {
ctx.statusElement.error('Could not find name of page creator');
return;
}
ctx.timestamp = $(xml).find('rev').attr('timestamp');
if (!ctx.timestamp) {
ctx.statusElement.error('Could not find timestamp of page creation');
return;
}
ctx.onLookupCreationSuccess(this);
} else {
ctx.lookupCreationApi.query.rvlimit = 50; // modify previous query to fetch more revisions
ctx.lookupCreationApi.query.titles = ctx.pageName; // update pageName if redirect resolution took place in earlier query
ctx.lookupCreationApi = new Morebits.wiki.api('دریافت اطلاعات ساخت صفحه', ctx.lookupCreationApi.query, fnLookupNonRedirectCreator, ctx.statusElement); // localized
ctx.lookupCreationApi.setParent(this);
ctx.lookupCreationApi.post();
}
};
var fnLookupNonRedirectCreator = function() {
var xml = ctx.lookupCreationApi.getXML();
$(xml).find('rev').each(function(_, rev) {
if (!/^\s*#redirect/i.test(rev.textContent)) { // inaccessible revisions also check out
ctx.creator = rev.getAttribute('user');
ctx.timestamp = rev.getAttribute('timestamp');
return false; // break
}
});
if (!ctx.creator) {
// fallback to give first revision author if no non-redirect version in the first 50
ctx.creator = $(xml).find('rev')[0].getAttribute('user');
ctx.timestamp = $(xml).find('rev')[0].getAttribute('timestamp');
if (!ctx.creator) {
ctx.statusElement.error('Could not find name of page creator');
return;
}
}
if (!ctx.timestamp) {
ctx.statusElement.error('Could not find timestamp of page creation');
return;
}
ctx.onLookupCreationSuccess(this);
};
var fnProcessMove = function() {
var pageTitle, token;
if (fnCanUseMwUserToken('move')) {
token = mw.user.tokens.get('csrfToken');
pageTitle = ctx.pageName;
} else {
var xml = ctx.moveApi.getXML();
if ($(xml).find('page').attr('missing') === '') {
ctx.statusElement.error('Cannot move the page, because it no longer exists');
ctx.onMoveFailure(this);
return;
}
// extract protection info
if (Morebits.userIsSysop) {
var editprot = $(xml).find('pr[type="edit"]');
if (editprot.length > 0 && editprot.attr('level') === 'sysop' && !ctx.suppressProtectWarning &&
!confirm('You are about to move the fully protected page "' + ctx.pageName +
(editprot.attr('expiry') === 'infinity' ? '" (protected indefinitely)' : '" (protection expiring ' + new Morebits.date(editprot.attr('expiry')).calendar('utc') + ' (UTC))') +
'. \n\nClick OK to proceed with the move, or Cancel to skip this move.')) {
ctx.statusElement.error('Move of fully protected page was aborted.');
ctx.onMoveFailure(this);
return;
}
}
token = $(xml).find('tokens').attr('csrftoken');
if (!token) {
ctx.statusElement.error('Failed to retrieve move token.');
ctx.onMoveFailure(this);
return;
}
pageTitle = $(xml).find('page').attr('title');
}
var query = {
'action': 'move',
'from': pageTitle,
'to': ctx.moveDestination,
'token': token,
'reason': ctx.editSummary,
'watchlist': ctx.watchlistOption
};
if (ctx.moveTalkPage) {
query.movetalk = 'true';
}
if (ctx.moveSubpages) {
query.movesubpages = 'true';
}
if (ctx.moveSuppressRedirect) {
query.noredirect = 'true';
}
ctx.moveProcessApi = new Morebits.wiki.api('moving page...', query, ctx.onMoveSuccess, ctx.statusElement, ctx.onMoveFailure);
ctx.moveProcessApi.setParent(this);
ctx.moveProcessApi.post();
};
var fnProcessPatrol = function() {
var query = {
action: 'patrol'
};
// Didn't need to load the page
if (ctx.rcid) {
query.rcid = ctx.rcid;
query.token = mw.user.tokens.get('patrolToken');
} else {
var xml = ctx.patrolApi.getResponse();
// Don't patrol if not unpatrolled
if ($(xml).find('rc').attr('unpatrolled') !== '') {
return;
}
var lastrevid = $(xml).find('page').attr('lastrevid');
if (!lastrevid) {
return;
}
query.revid = lastrevid;
var token = $(xml).find('tokens').attr('patroltoken');
if (!token) {
return;
}
query.token = token;
}
var patrolStat = new Morebits.status('Marking page as patrolled');
ctx.patrolProcessApi = new Morebits.wiki.api('patrolling page...', query, null, patrolStat);
ctx.patrolProcessApi.setParent(this);
ctx.patrolProcessApi.post();
};
var fnProcessTriage = function() {
var pageID, token;
if (ctx.pageID) {
token = mw.user.tokens.get('csrfToken');
pageID = ctx.pageID;
} else {
var xml = ctx.triageApi.getXML();
pageID = $(xml).find('page').attr('pageid');
if (!pageID) {
return;
}
token = $(xml).find('tokens').attr('csrftoken');
if (!token) {
return;
}
}
var query = {
action: 'pagetriageaction',
pageid: pageID,
reviewed: 1,
token: token
};
var triageStat = new Morebits.status('Marking page as curated');
ctx.triageProcessApi = new Morebits.wiki.api('curating page...', query, null, triageStat, fnProcessTriageError);
ctx.triageProcessApi.setParent(this);
ctx.triageProcessApi.post();
};
// callback from triageProcessApi.post()
var fnProcessTriageError = function() {
// Ignore error if page not in queue, see https://github.com/azatoth/twinkle/pull/930
if (ctx.triageProcessApi.getErrorCode() === 'bad-pagetriage-page') {
ctx.triageProcessApi.getStatusElement().unlink();
}
};
var fnProcessDelete = function() {
var pageTitle, token;
if (fnCanUseMwUserToken('delete')) {
token = mw.user.tokens.get('csrfToken');
pageTitle = ctx.pageName;
} else {
var xml = ctx.deleteApi.getXML();
if ($(xml).find('page').attr('missing') === '') {
ctx.statusElement.error('Cannot delete the page, because it no longer exists');
ctx.onDeleteFailure(this);
return;
}
// extract protection info
var editprot = $(xml).find('pr[type="edit"]');
if (editprot.length > 0 && editprot.attr('level') === 'sysop' && !ctx.suppressProtectWarning &&
!confirm('You are about to delete the fully protected page "' + ctx.pageName +
(editprot.attr('expiry') === 'infinity' ? '" (protected indefinitely)' : '" (protection expiring ' + new Morebits.date(editprot.attr('expiry')).calendar('utc') + ' (UTC))') +
'. \n\nClick OK to proceed with the deletion, or Cancel to skip this deletion.')) {
ctx.statusElement.error('Deletion of fully protected page was aborted.');
ctx.onDeleteFailure(this);
return;
}
token = $(xml).find('tokens').attr('csrftoken');
if (!token) {
ctx.statusElement.error('Failed to retrieve delete token.');
ctx.onDeleteFailure(this);
return;
}
pageTitle = $(xml).find('page').attr('title');
}
var query = {
'action': 'delete',
'title': pageTitle,
'token': token,
'reason': ctx.editSummary,
'watchlist': ctx.watchlistOption
};
ctx.deleteProcessApi = new Morebits.wiki.api('deleting page...', query, ctx.onDeleteSuccess, ctx.statusElement, fnProcessDeleteError);
ctx.deleteProcessApi.setParent(this);
ctx.deleteProcessApi.post();
};
// callback from deleteProcessApi.post()
var fnProcessDeleteError = function() {
var errorCode = ctx.deleteProcessApi.getErrorCode();
// check for "Database query error"
if (errorCode === 'internal_api_error_DBQueryError' && ctx.retries++ < ctx.maxRetries) {
ctx.statusElement.info('Database query error, retrying');
--Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds
ctx.deleteProcessApi.post(); // give it another go!
} else if (errorCode === 'badtoken' && ctx.retries++ < ctx.maxRetries) {
ctx.statusElement.info('Invalid token, retrying');
--Morebits.wiki.numberOfActionsLeft;
fnGetToken().then(function(token) {
ctx.deleteProcessApi.query.token = token;
ctx.deleteProcessApi.post();
});
} else if (errorCode === 'missingtitle') {
ctx.statusElement.error('Cannot delete the page, because it no longer exists');
if (ctx.onDeleteFailure) {
ctx.onDeleteFailure.call(this, ctx.deleteProcessApi); // invoke callback
}
// hard error, give up
} else {
ctx.statusElement.error('Failed to delete the page: ' + ctx.deleteProcessApi.getErrorText());
if (ctx.onDeleteFailure) {
ctx.onDeleteFailure.call(this, ctx.deleteProcessApi); // invoke callback
}
}
};
var fnProcessUndelete = function() {
var pageTitle, token;
if (fnCanUseMwUserToken('undelete')) {
token = mw.user.tokens.get('csrfToken');
pageTitle = ctx.pageName;
} else {
var xml = ctx.undeleteApi.getXML();
if ($(xml).find('page').attr('missing') !== '') {
ctx.statusElement.error('Cannot undelete the page, because it already exists');
ctx.onUndeleteFailure(this);
return;
}
// extract protection info
var editprot = $(xml).find('pr[type="create"]');
if (editprot.length > 0 && editprot.attr('level') === 'sysop' && !ctx.suppressProtectWarning &&
!confirm('You are about to undelete the fully create protected page "' + ctx.pageName +
(editprot.attr('expiry') === 'infinity' ? '" (protected indefinitely)' : '" (protection expiring ' + new Morebits.date(editprot.attr('expiry')).calendar('utc') + ' (UTC))') +
'. \n\nClick OK to proceed with the undeletion, or Cancel to skip this undeletion.')) {
ctx.statusElement.error('Undeletion of fully create protected page was aborted.');
ctx.onUndeleteFailure(this);
return;
}
token = $(xml).find('tokens').attr('csrftoken');
if (!token) {
ctx.statusElement.error('Failed to retrieve undelete token.');
ctx.onUndeleteFailure(this);
return;
}
pageTitle = $(xml).find('page').attr('title');
}
var query = {
'action': 'undelete',
'title': pageTitle,
'token': token,
'reason': ctx.editSummary,
'watchlist': ctx.watchlistOption
};
ctx.undeleteProcessApi = new Morebits.wiki.api('undeleting page...', query, ctx.onUndeleteSuccess, ctx.statusElement, fnProcessUndeleteError);
ctx.undeleteProcessApi.setParent(this);
ctx.undeleteProcessApi.post();
};
// callback from undeleteProcessApi.post()
var fnProcessUndeleteError = function() {
var errorCode = ctx.undeleteProcessApi.getErrorCode();
// check for "Database query error"
if (errorCode === 'internal_api_error_DBQueryError' && ctx.retries++ < ctx.maxRetries) {
ctx.statusElement.info('Database query error, retrying');
--Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds
ctx.undeleteProcessApi.post(); // give it another go!
} else if (errorCode === 'badtoken' && ctx.retries++ < ctx.maxRetries) {
ctx.statusElement.info('Invalid token, retrying');
--Morebits.wiki.numberOfActionsLeft;
fnGetToken().then(function(token) {
ctx.undeleteProcessApi.query.token = token;
ctx.undeleteProcessApi.post();
});
} else if (errorCode === 'cantundelete') {
ctx.statusElement.error('Cannot undelete the page, either because there are no revisions to undelete or because it has already been undeleted');
if (ctx.onUndeleteFailure) {
ctx.onUndeleteFailure.call(this, ctx.undeleteProcessApi); // invoke callback
}
// hard error, give up
} else {
ctx.statusElement.error('Failed to undelete the page: ' + ctx.undeleteProcessApi.getErrorText());
if (ctx.onUndeleteFailure) {
ctx.onUndeleteFailure.call(this, ctx.undeleteProcessApi); // invoke callback
}
}
};
var fnProcessProtect = function() {
var xml = ctx.protectApi.getXML();
var missing = $(xml).find('page').attr('missing') === '';
if ((ctx.protectEdit || ctx.protectMove) && missing) {
ctx.statusElement.error('Cannot protect the page, because it no longer exists');
ctx.onProtectFailure(this);
return;
}
if (ctx.protectCreate && !missing) {
ctx.statusElement.error('Cannot create protect the page, because it already exists');
ctx.onProtectFailure(this);
return;
}
// TODO cascading protection not possible on edit<sysop
var token = $(xml).find('tokens').attr('csrftoken');
if (!token) {
ctx.statusElement.error('Failed to retrieve protect token.');
ctx.onProtectFailure(this);
return;
}
var pageTitle = $(xml).find('page').attr('title');
// fetch existing protection levels
var prs = $(xml).find('pr');
var editprot = prs.filter('[type="edit"]');
var moveprot = prs.filter('[type="move"]');
var createprot = prs.filter('[type="create"]');
var protections = [], expirys = [];
// set edit protection level
if (ctx.protectEdit) {
protections.push('edit=' + ctx.protectEdit.level);
expirys.push(ctx.protectEdit.expiry);
} else if (editprot.length) {
protections.push('edit=' + editprot.attr('level'));
expirys.push(editprot.attr('expiry').replace('infinity', 'indefinite'));
}
if (ctx.protectMove) {
protections.push('move=' + ctx.protectMove.level);
expirys.push(ctx.protectMove.expiry);
} else if (moveprot.length) {
protections.push('move=' + moveprot.attr('level'));
expirys.push(moveprot.attr('expiry').replace('infinity', 'indefinite'));
}
if (ctx.protectCreate) {
protections.push('create=' + ctx.protectCreate.level);
expirys.push(ctx.protectCreate.expiry);
} else if (createprot.length) {
protections.push('create=' + createprot.attr('level'));
expirys.push(createprot.attr('expiry').replace('infinity', 'indefinite'));
}
var query = {
action: 'protect',
title: pageTitle,
token: token,
protections: protections.join('|'),
expiry: expirys.join('|'),
reason: ctx.editSummary,
watchlist: ctx.watchlistOption
};
if (ctx.protectCascade) {
query.cascade = 'true';
}
ctx.protectProcessApi = new Morebits.wiki.api('protecting page...', query, ctx.onProtectSuccess, ctx.statusElement, ctx.onProtectFailure);
ctx.protectProcessApi.setParent(this);
ctx.protectProcessApi.post();
};
var fnProcessStabilize = function() {
var pageTitle, token;
if (fnCanUseMwUserToken('stabilize')) {
token = mw.user.tokens.get('csrfToken');
pageTitle = ctx.pageName;
} else {
var xml = ctx.stabilizeApi.getXML();
var missing = $(xml).find('page').attr('missing') === '';
if (missing) {
ctx.statusElement.error('Cannot protect the page, because it no longer exists');
ctx.onStabilizeFailure(this);
return;
}
token = $(xml).find('tokens').attr('csrftoken');
if (!token) {
ctx.statusElement.error('Failed to retrieve stabilize token.');
ctx.onStabilizeFailure(this);
return;
}
pageTitle = $(xml).find('page').attr('title');
}
var query = {
action: 'stabilize',
title: pageTitle,
token: token,
protectlevel: ctx.flaggedRevs.level,
expiry: ctx.flaggedRevs.expiry,
reason: ctx.editSummary
};
// [[phab:T247915]]
if (ctx.watchlistOption === 'watch') {
query.watchlist = 'true';
}
ctx.stabilizeProcessApi = new Morebits.wiki.api('configuring stabilization settings...', query, ctx.onStabilizeSuccess, ctx.statusElement, ctx.onStabilizeFailure);
ctx.stabilizeProcessApi.setParent(this);
ctx.stabilizeProcessApi.post();
};
}; // end Morebits.wiki.page
/* Morebits.wiki.page TODO: (XXX)
* - Should we retry loads also?
* - Need to reset current action before the save?
* - Deal with action.completed stuff
* - Need to reset all parameters once done (e.g. edit summary, move destination, etc.)
*/
/**
* **************** Morebits.wiki.preview ****************
* Uses the API to parse a fragment of wikitext and render it as HTML.
*
* The suggested implementation pattern (in Morebits.simpleWindow + Morebits.quickForm situations) is to
* construct a Morebits.wiki.preview object after rendering a Morebits.quickForm, and bind the object
* to an arbitrary property of the form (e.g. |previewer|). For an example, see
* twinklewarn.js.
*/
/**
* @constructor
* @param {HTMLElement} previewbox - the element that will contain the rendered HTML,
* usually a <div> element
*/
Morebits.wiki.preview = function(previewbox) {
this.previewbox = previewbox;
$(previewbox).addClass('morebits-previewbox').hide();
/**
* Displays the preview box, and begins an asynchronous attempt
* to render the specified wikitext.
* @param {string} wikitext - wikitext to render; most things should work, including subst: and ~~~~
* @param {string} [pageTitle] - optional parameter for the page this should be rendered as being on, if omitted it is taken as the current page
*/
this.beginRender = function(wikitext, pageTitle) {
$(previewbox).show();
var statusspan = document.createElement('span');
previewbox.appendChild(statusspan);
Morebits.status.init(statusspan);
var query = {
action: 'parse',
prop: 'text',
pst: 'true', // PST = pre-save transform; this makes substitution work properly
text: wikitext,
title: pageTitle || mw.config.get('wgPageName')
};
var renderApi = new Morebits.wiki.api('loading...', query, fnRenderSuccess, new Morebits.status('Preview'));
renderApi.post();
};
var fnRenderSuccess = function(apiobj) {
var xml = apiobj.getXML();
var html = $(xml).find('text').text();
if (!html) {
apiobj.statelem.error('failed to retrieve preview, or template was blanked');
return;
}
previewbox.innerHTML = html;
$(previewbox).find('a').attr('target', '_blank'); // this makes links open in new tab
};
/** Hides the preview box and clears it. */
this.closePreview = function() {
$(previewbox).empty().hide();
};
};
/**
* **************** Morebits.wikitext ****************
* Wikitext manipulation
*/
Morebits.wikitext = {};
Morebits.wikitext.template = {
parse: function(text, start) {
var count = -1;
var level = -1;
var equals = -1;
var current = '';
var result = {
name: '',
parameters: {}
};
var key, value;
for (var i = start; i < text.length; ++i) {
var test3 = text.substr(i, 3);
if (test3 === '{{{') {
current += '{{{';
i += 2;
++level;
continue;
}
if (test3 === '}}}') {
current += '}}}';
i += 2;
--level;
continue;
}
var test2 = text.substr(i, 2);
if (test2 === '{{' || test2 === '[[') {
current += test2;
++i;
++level;
continue;
}
if (test2 === ']]') {
current += ']]';
++i;
--level;
continue;
}
if (test2 === '}}') {
current += test2;
++i;
--level;
if (level <= 0) {
if (count === -1) {
result.name = current.substring(2).trim();
++count;
} else {
if (equals !== -1) {
key = current.substring(0, equals).trim();
value = current.substring(equals).trim();
result.parameters[key] = value;
equals = -1;
} else {
result.parameters[count] = current;
++count;
}
}
break;
}
continue;
}
if (text.charAt(i) === '|' && level <= 0) {
if (count === -1) {
result.name = current.substring(2).trim();
++count;
} else {
if (equals !== -1) {
key = current.substring(0, equals).trim();
value = current.substring(equals + 1).trim();
result.parameters[key] = value;
equals = -1;
} else {
result.parameters[count] = current;
++count;
}
}
current = '';
} else if (equals === -1 && text.charAt(i) === '=' && level <= 0) {
equals = current.length;
current += text.charAt(i);
} else {
current += text.charAt(i);
}
}
return result;
}
};
/**
* @constructor
* @param {string} text
*/
Morebits.wikitext.page = function mediawikiPage(text) {
this.text = text;
};
Morebits.wikitext.page.prototype = {
text: '',
/**
* Removes links to `link_target` from the page text.
* @param {string} link_target
*/
removeLink: function(link_target) {
var first_char = link_target.substr(0, 1);
var link_re_string = '[' + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape(link_target.substr(1), true);
// Files and Categories become links with a leading colon, e.g. [[:File:Test.png]]
// Otherwise, allow for an optional leading colon, e.g. [[:User:Test]]
var special_ns_re = /^(?:[Ff]ile|[Ii]mage|[Cc]ategory|پرونده|تصویر|رده):/; // localized
var colon = special_ns_re.test(link_target) ? ':' : ':?';
var link_simple_re = new RegExp('\\[\\[' + colon + '(' + link_re_string + ')\\]\\]', 'g');
var link_named_re = new RegExp('\\[\\[' + colon + link_re_string + '\\|(.+?)\\]\\]', 'g');
this.text = this.text.replace(link_simple_re, '$1').replace(link_named_re, '$1');
},
/**
* Comments out images from page text. If used in a gallery, deletes the whole line.
* If used as a template argument (not necessarily with File: prefix), the template parameter is commented out.
* @param {string} image - Image name without File: prefix
* @param {string} reason - Reason to be included in comment, alongside the commented-out image
*/
commentOutImage: function(image, reason) {
var unbinder = new Morebits.unbinder(this.text);
unbinder.unbind('<!--', '-->');
reason = reason ? reason + ': ' : '';
var first_char = image.substr(0, 1);
var image_re_string = '[' + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape(image.substr(1), true);
// Check for normal image links, i.e. [[File:Foobar.png|...]]
// Will eat the whole link
var links_re = new RegExp('\\[\\[(?:[Ii]mage|[Ff]ile|تصویر|پرونده):\\s*' + image_re_string); // localized
var allLinks = Morebits.array.uniq(Morebits.string.splitWeightedByKeys(unbinder.content, '[[', ']]'));
for (var i = 0; i < allLinks.length; ++i) {
if (links_re.test(allLinks[i])) {
var replacement = '<!-- ' + reason + allLinks[i] + ' -->';
unbinder.content = unbinder.content.replace(allLinks[i], replacement, 'g');
}
}
// unbind the newly created comments
unbinder.unbind('<!--', '-->');
// Check for gallery images, i.e. instances that must start on a new line,
// eventually preceded with some space, and must include File: prefix
// Will eat the whole line.
var gallery_image_re = new RegExp('(^\\s*(?:[Ii]mage|[Ff]ile|تصویر|پرونده):\\s*' + image_re_string + '.*?$)', 'mg'); // localized
unbinder.content = unbinder.content.replace(gallery_image_re, '<!-- ' + reason + '$1 -->');
// unbind the newly created comments
unbinder.unbind('<!--', '-->');
// Check free image usages, for example as template arguments, might have the File: prefix excluded, but must be preceeded by an |
// Will only eat the image name and the preceeding bar and an eventual named parameter
var free_image_re = new RegExp('(\\|\\s*(?:[\\w\\s]+\\=)?\\s*(?:(?:[Ii]mage|[Ff]ile|تصویر|پرونده):\\s*)?' + image_re_string + ')', 'mg'); // localized
unbinder.content = unbinder.content.replace(free_image_re, '<!-- ' + reason + '$1 -->');
// Rebind the content now, we are done!
this.text = unbinder.rebind();
},
/**
* Converts first usage of [[File:`image`]] to [[File:`image`|`data`]]
* @param {string} image - Image name without File: prefix
* @param {string} data
*/
addToImageComment: function(image, data) {
var first_char = image.substr(0, 1);
var first_char_regex = RegExp.escape(first_char, true);
if (first_char.toUpperCase() !== first_char.toLowerCase()) {
first_char_regex = '[' + RegExp.escape(first_char.toUpperCase(), true) + RegExp.escape(first_char.toLowerCase(), true) + ']';
}
var image_re_string = '(?:[Ii]mage|[Ff]ile|تصویر|پرونده):\\s*' + first_char_regex + RegExp.escape(image.substr(1), true); // localized
var links_re = new RegExp('\\[\\[' + image_re_string);
var allLinks = Morebits.array.uniq(Morebits.string.splitWeightedByKeys(this.text, '[[', ']]'));
for (var i = 0; i < allLinks.length; ++i) {
if (links_re.test(allLinks[i])) {
var replacement = allLinks[i];
// just put it at the end?
replacement = replacement.replace(/\]\]$/, '|' + data + ']]');
this.text = this.text.replace(allLinks[i], replacement, 'g');
}
}
var gallery_re = new RegExp('^(\\s*' + image_re_string + '.*?)\\|?(.*?)$', 'mg');
var newtext = '$1|$2 ' + data;
this.text = this.text.replace(gallery_re, newtext);
},
/**
* Removes transclusions of template from page text
* @param {string} template - Page name whose transclusions are to be removed,
* include namespace prefix only if not in template namespace
*/
removeTemplate: function(template) {
var first_char = template.substr(0, 1);
var template_re_string = '(?:[Tt]emplate:)?\\s*[' + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape(template.substr(1), true);
var links_re = new RegExp('\\{\\{' + template_re_string);
var allTemplates = Morebits.array.uniq(Morebits.string.splitWeightedByKeys(this.text, '{{', '}}', [ '{{{', '}}}' ]));
for (var i = 0; i < allTemplates.length; ++i) {
if (links_re.test(allTemplates[i])) {
this.text = this.text.replace(allTemplates[i], '', 'g');
}
}
},
/** @returns {string} */
getText: function() {
return this.text;
}
};
/**
* *********** Morebits.userspaceLogger ************
* Handles logging actions to a userspace log, used in
* twinklespeedy and twinkleprod.
*/
Morebits.userspaceLogger = function(logPageName) {
if (!logPageName) {
throw new Error('no log page name specified');
}
this.initialText = '';
this.headerLevel = 3;
this.log = function(logText, summaryText) {
if (!logText) {
return;
}
var page = new Morebits.wiki.page('User:' + mw.config.get('wgUserName') + '/' + logPageName,
'افزودن مورد به سیاههٔ کاربر'); // make this '... to ' + logPageName ? // localized
return page.load(function(pageobj) {
// add blurb if log page doesn't exist or is blank
var text = pageobj.getPageText() || this.initialText;
// create monthly header if it doesn't exist already
var date = new Morebits.date(pageobj.getLoadTime());
if (!date.monthHeaderRegex().exec(text)) {
text += '\n\n' + date.monthHeader(this.headerLevel);
}
pageobj.setPageText(text + '\n' + logText);
pageobj.setEditSummary(summaryText);
pageobj.setCreateOption('recreate');
pageobj.save();
}.bind(this));
};
};
/**
* **************** Morebits.status ****************
*/
/**
* @constructor
* Morebits.status.init() must be called before any status object is created, otherwise
* those statuses won't be visible.
* @param {String} text - Text before the the colon `:`
* @param {String} stat - Text after the colon `:`
* @param {String} [type=status] - This parameter determines the font color of the status line,
* this can be 'status' (blue), 'info' (green), 'warn' (red), or 'error' (bold red)
* The default is 'status'
*/
Morebits.status = function Status(text, stat, type) {
this.textRaw = text;
this.text = this.codify(text);
this.type = type || 'status';
this.generate();
if (stat) {
this.update(stat, type);
}
};
/**
* Specify an area for status message elements to be added to
* @param {HTMLElement} root - usually a div element
*/
Morebits.status.init = function(root) {
if (!(root instanceof Element)) {
throw new Error('object not an instance of Element');
}
while (root.hasChildNodes()) {
root.removeChild(root.firstChild);
}
Morebits.status.root = root;
Morebits.status.errorEvent = null;
};
Morebits.status.root = null;
/** @param {Function} handler - function to execute on error */
Morebits.status.onError = function(handler) {
if (typeof handler === 'function') {
Morebits.status.errorEvent = handler;
} else {
throw 'Morebits.status.onError: handler is not a function';
}
};
Morebits.status.prototype = {
stat: null,
text: null,
textRaw: null,
type: 'status',
target: null,
node: null,
linked: false,
/** Add the status element node to the DOM */
link: function() {
if (!this.linked && Morebits.status.root) {
Morebits.status.root.appendChild(this.node);
this.linked = true;
}
},
/** Remove the status element node from the DOM */
unlink: function() {
if (this.linked) {
Morebits.status.root.removeChild(this.node);
this.linked = false;
}
},
/**
* Create a document fragment with the status text
* @param {(string|Element|Array)} obj
* @returns {DocumentFragment}
*/
codify: function(obj) {
if (!Array.isArray(obj)) {
obj = [ obj ];
}
var result;
result = document.createDocumentFragment();
for (var i = 0; i < obj.length; ++i) {
if (typeof obj[i] === 'string') {
result.appendChild(document.createTextNode(obj[i]));
} else if (obj[i] instanceof Element) {
result.appendChild(obj[i]);
} // Else cosmic radiation made something shit
}
return result;
},
/**
* Update the status
* @param {String} status - Part of status message after colon `:`
* @param {String} type - 'status' (blue), 'info' (green), 'warn' (red), or 'error' (bold red)
*/
update: function(status, type) {
this.stat = this.codify(status);
if (type) {
this.type = type;
if (type === 'error') {
// hack to force the page not to reload when an error is output - see also Morebits.status() above
Morebits.wiki.numberOfActionsLeft = 1000;
// call error callback
if (Morebits.status.errorEvent) {
Morebits.status.errorEvent();
}
// also log error messages in the browser console
console.error(this.textRaw + ': ' + status); // eslint-disable-line no-console
}
}
this.render();
},
/** Produce the html for first part of the status message */
generate: function() {
this.node = document.createElement('div');
this.node.appendChild(document.createElement('span')).appendChild(this.text);
this.node.appendChild(document.createElement('span')).appendChild(document.createTextNode(': '));
this.target = this.node.appendChild(document.createElement('span'));
this.target.appendChild(document.createTextNode('')); // dummy node
},
/** Complete the html, for the second part of the status message */
render: function() {
this.node.className = 'morebits_status_' + this.type;
while (this.target.hasChildNodes()) {
this.target.removeChild(this.target.firstChild);
}
this.target.appendChild(this.stat);
this.link();
},
status: function(status) {
this.update(status, 'status');
},
info: function(status) {
this.update(status, 'info');
},
warn: function(status) {
this.update(status, 'warn');
},
error: function(status) {
this.update(status, 'error');
}
};
Morebits.status.info = function(text, status) {
return new Morebits.status(text, status, 'info');
};
Morebits.status.warn = function(text, status) {
return new Morebits.status(text, status, 'warn');
};
Morebits.status.error = function(text, status) {
return new Morebits.status(text, status, 'error');
};
/**
* For the action complete message at the end, create a status line without
* a colon separator.
* @param {String} text
*/
Morebits.status.actionCompleted = function(text) {
var node = document.createElement('div');
node.appendChild(document.createElement('span')).appendChild(document.createTextNode(text));
node.className = 'morebits_status_info';
if (Morebits.status.root) {
Morebits.status.root.appendChild(node);
}
};
/**
* Display the user's rationale, comments, etc. back to them after a failure,
* so that they may re-use it
* @param {string} comments
* @param {string} message
*/
Morebits.status.printUserText = function(comments, message) {
var p = document.createElement('p');
p.innerHTML = message;
var div = document.createElement('div');
div.className = 'toccolours';
div.style.marginTop = '0';
div.style.whiteSpace = 'pre-wrap';
div.textContent = comments;
p.appendChild(div);
Morebits.status.root.appendChild(p);
};
/**
* **************** Morebits.htmlNode() ****************
* Simple helper function to create a simple node
* @param {string} type - type of HTML element
* @param {string} text - text content
* @param {string} [color] - font color
* @returns {HTMLElement}
*/
Morebits.htmlNode = function (type, content, color) {
var node = document.createElement(type);
if (color) {
node.style.color = color;
}
node.appendChild(document.createTextNode(content));
return node;
};
/**
* **************** Morebits.checkboxShiftClickSupport() ****************
* shift-click-support for checkboxes
* wikibits version (window.addCheckboxClickHandlers) has some restrictions, and
* doesn't work with checkboxes inside a sortable table, so let's build our own.
*/
Morebits.checkboxShiftClickSupport = function (jQuerySelector, jQueryContext) {
var lastCheckbox = null;
function clickHandler(event) {
var thisCb = this;
if (event.shiftKey && lastCheckbox !== null) {
var cbs = $(jQuerySelector, jQueryContext); // can't cache them, obviously, if we want to support resorting
var index = -1, lastIndex = -1, i;
for (i = 0; i < cbs.length; i++) {
if (cbs[i] === thisCb) {
index = i;
if (lastIndex > -1) {
break;
}
}
if (cbs[i] === lastCheckbox) {
lastIndex = i;
if (index > -1) {
break;
}
}
}
if (index > -1 && lastIndex > -1) {
// inspired by wikibits
var endState = thisCb.checked;
var start, finish;
if (index < lastIndex) {
start = index + 1;
finish = lastIndex;
} else {
start = lastIndex;
finish = index - 1;
}
for (i = start; i <= finish; i++) {
if (cbs[i].checked !== endState) {
cbs[i].click();
}
}
}
}
lastCheckbox = thisCb;
return true;
}
$(jQuerySelector, jQueryContext).click(clickHandler);
};
/** **************** Morebits.batchOperation ****************
* Iterates over a group of pages (or arbitrary objects) and executes a worker function
* for each.
*
* Constructor: Morebits.batchOperation(currentAction)
*
* setPageList(wikitext): Sets the list of pages to work on.
* It should be an array of page names (strings).
*
* setOption(optionName, optionValue): Sets a known option:
* - chunkSize (integer): the size of chunks to break the array into (default 50).
* Setting this to a small value (<5) can cause problems.
* - preserveIndividualStatusLines (boolean): keep each page's status element visible
* when worker is complete? See note below
*
* run(worker, postFinish): Runs the callback `worker` for each page in the list.
* The callback must call workerSuccess when succeeding, or workerFailure
* when failing. If using Morebits.wiki.api or Morebits.wiki.page, this is easily
* done by passing these two functions as parameters to the methods on those
* objects, for instance, page.save(batchOp.workerSuccess, batchOp.workerFailure).
* Make sure the methods are called directly if special success/failure cases arise.
* If you omit to call these methods, the batch operation will stall after the first
* chunk! Also ensure that either workerSuccess or workerFailure is called no more
* than once.
* The second callback `postFinish` is executed when the entire batch has been processed.
*
* If using preserveIndividualStatusLines, you should try to ensure that the
* workerSuccess callback has access to the page title. This is no problem for
* Morebits.wiki.page objects. But when using the API, please set the
* |pageName| property on the Morebits.wiki.api object.
*
* There are sample batchOperation implementations using Morebits.wiki.page in
* twinklebatchdelete.js, twinklebatchundelete.js, and twinklebatchprotect.js.
*/
/**
* @constructor
* @param {string} [currentAction]
*/
Morebits.batchOperation = function(currentAction) {
var ctx = {
// backing fields for public properties
pageList: null,
options: {
chunkSize: 50,
preserveIndividualStatusLines: false
},
// internal counters, etc.
statusElement: new Morebits.status(currentAction || 'Performing batch operation'),
worker: null, // function that executes for each item in pageList
postFinish: null, // function that executes when the whole batch has been processed
countStarted: 0,
countFinished: 0,
countFinishedSuccess: 0,
currentChunkIndex: -1,
pageChunks: [],
running: false
};
// shouldn't be needed by external users, but provided anyway for maximum flexibility
this.getStatusElement = function() {
return ctx.statusElement;
};
/**
* Sets the list of pages to work on
* @param {Array} pageList Array of objects over which you wish to execute the worker function
* This is usually the list of page names (strings).
*/
this.setPageList = function(pageList) {
ctx.pageList = pageList;
};
/**
* Sets a known option:
* - chunkSize (integer):
* The size of chunks to break the array into (default 50).
* Setting this to a small value (<5) can cause problems.
* - preserveIndividualStatusLines (boolean):
* Keep each page's status element visible when worker is complete?
*/
this.setOption = function(optionName, optionValue) {
ctx.options[optionName] = optionValue;
};
/**
* Runs the first callback for each page in the list.
* The callback must call workerSuccess when succeeding, or workerFailure when failing.
* Runs the second callback when the whole batch has been processed (optional)
* @param {Function} worker
* @param {Function} [postFinish]
*/
this.run = function(worker, postFinish) {
if (ctx.running) {
ctx.statusElement.error('Batch operation is already running');
return;
}
ctx.running = true;
ctx.worker = worker;
ctx.postFinish = postFinish;
ctx.countStarted = 0;
ctx.countFinished = 0;
ctx.countFinishedSuccess = 0;
ctx.currentChunkIndex = -1;
ctx.pageChunks = [];
var total = ctx.pageList.length;
if (!total) {
ctx.statusElement.info('no pages specified');
ctx.running = false;
if (ctx.postFinish) {
ctx.postFinish();
}
return;
}
// chunk page list into more manageable units
ctx.pageChunks = Morebits.array.chunk(ctx.pageList, ctx.options.chunkSize);
// start the process
Morebits.wiki.addCheckpoint();
ctx.statusElement.status('0%');
fnStartNewChunk();
};
/**
* To be called by worker before it terminates succesfully
* @param {(Morebits.wiki.page|Morebits.wiki.api|string)} arg
* This should be the `Morebits.wiki.page` or `Morebits.wiki.api` object used by worker
* (for the adjustment of status lines emitted by them).
* If no Morebits.wiki.* object is used (eg. you're using mw.Api() or something else), and
* `preserveIndividualStatusLines` option is on, give the page name (string) as argument.
*/
this.workerSuccess = function(arg) {
var createPageLink = function(pageName) {
var link = document.createElement('a');
link.setAttribute('href', mw.util.getUrl(pageName));
link.appendChild(document.createTextNode(pageName));
return link;
};
if (arg instanceof Morebits.wiki.api || arg instanceof Morebits.wiki.page) {
// update or remove status line
var statelem = arg.getStatusElement();
if (ctx.options.preserveIndividualStatusLines) {
if (arg.getPageName || arg.pageName || (arg.query && arg.query.title)) {
// we know the page title - display a relevant message
var pageName = arg.getPageName ? arg.getPageName() : arg.pageName || arg.query.title;
statelem.info(['completed (', createPageLink(pageName), ')']);
} else {
// we don't know the page title - just display a generic message
statelem.info('انجام شد'); // localized
}
} else {
// remove the status line automatically produced by Morebits.wiki.*
statelem.unlink();
}
} else if (typeof arg === 'string' && ctx.options.preserveIndividualStatusLines) {
new Morebits.status(arg, ['done (', createPageLink(arg), ')']);
}
ctx.countFinishedSuccess++;
fnDoneOne();
};
this.workerFailure = function() {
fnDoneOne();
};
// private functions
var thisProxy = this;
var fnStartNewChunk = function() {
var chunk = ctx.pageChunks[++ctx.currentChunkIndex];
if (!chunk) {
return; // done! yay
}
// start workers for the current chunk
ctx.countStarted += chunk.length;
chunk.forEach(function(page) {
ctx.worker(page, thisProxy);
});
};
var fnDoneOne = function() {
ctx.countFinished++;
// update overall status line
var total = ctx.pageList.length;
if (ctx.countFinished === total) {
var statusString = 'Done (' + ctx.countFinishedSuccess +
'/' + ctx.countFinished + ' actions completed successfully)';
if (ctx.countFinishedSuccess < ctx.countFinished) {
ctx.statusElement.warn(statusString);
} else {
ctx.statusElement.info(statusString);
}
if (ctx.postFinish) {
ctx.postFinish();
}
Morebits.wiki.removeCheckpoint();
ctx.running = false;
return;
}
// just for giggles! (well, serious debugging, actually)
if (ctx.countFinished > total) {
ctx.statusElement.warn('Done (overshot by ' + (ctx.countFinished - total) + ')');
Morebits.wiki.removeCheckpoint();
ctx.running = false;
return;
}
ctx.statusElement.status(parseInt(100 * ctx.countFinished / total, 10) + '%');
// start a new chunk if we're close enough to the end of the previous chunk, and
// we haven't already started the next one
if (ctx.countFinished >= (ctx.countStarted - Math.max(ctx.options.chunkSize / 10, 2)) &&
Math.floor(ctx.countFinished / ctx.options.chunkSize) > ctx.currentChunkIndex) {
fnStartNewChunk();
}
};
};
/**
* **************** Morebits.simpleWindow ****************
* A simple draggable window
* now a wrapper for jQuery UI's dialog feature
* @requires {jquery.ui.dialog}
*/
/**
* @constructor
* @param {number} width
* @param {number} height The maximum allowable height for the content area.
*/
Morebits.simpleWindow = function SimpleWindow(width, height) {
var content = document.createElement('div');
this.content = content;
content.className = 'morebits-dialog-content';
content.id = 'morebits-dialog-content-' + Math.round(Math.random() * 1e15);
this.height = height;
$(this.content).dialog({
autoOpen: false,
buttons: { 'Placeholder button': function() {} },
dialogClass: 'morebits-dialog',
width: Math.min(parseInt(window.innerWidth, 10), parseInt(width ? width : 800, 10)),
// give jQuery the given height value (which represents the anticipated height of the dialog) here, so
// it can position the dialog appropriately
// the 20 pixels represents adjustment for the extra height of the jQuery dialog "chrome", compared
// to that of the old SimpleWindow
height: height + 20,
close: function(event) {
// dialogs and their content can be destroyed once closed
$(event.target).dialog('destroy').remove();
},
resizeStart: function() {
this.scrollbox = $(this).find('.morebits-scrollbox')[0];
if (this.scrollbox) {
this.scrollbox.style.maxHeight = 'none';
}
},
resizeEnd: function() {
this.scrollbox = null;
},
resize: function() {
this.style.maxHeight = '';
if (this.scrollbox) {
this.scrollbox.style.width = '';
}
}
});
var $widget = $(this.content).dialog('widget');
// delete the placeholder button (it's only there so the buttonpane gets created)
$widget.find('button').each(function(key, value) {
value.parentNode.removeChild(value);
});
// add container for the buttons we add, and the footer links (if any)
var buttonspan = document.createElement('span');
buttonspan.className = 'morebits-dialog-buttons';
var linksspan = document.createElement('span');
linksspan.className = 'morebits-dialog-footerlinks';
$widget.find('.ui-dialog-buttonpane').append(buttonspan, linksspan);
// resize the scrollbox with the dialog, if one is present
$widget.resizable('option', 'alsoResize', '#' + this.content.id + ' .morebits-scrollbox, #' + this.content.id);
};
Morebits.simpleWindow.prototype = {
buttons: [],
height: 600,
hasFooterLinks: false,
scriptName: null,
/**
* Focuses the dialog. This might work, or on the contrary, it might not.
* @returns {Morebits.simpleWindow}
*/
focus: function() {
$(this.content).dialog('moveToTop');
return this;
},
/**
* Closes the dialog. If this is set as an event handler, it will stop the event
* from doing anything more.
* @returns {Morebits.simpleWindow}
*/
close: function(event) {
if (event) {
event.preventDefault();
}
$(this.content).dialog('close');
return this;
},
/**
* Shows the dialog. Calling display() on a dialog that has previously been closed
* might work, but it is not guaranteed.
* @returns {Morebits.simpleWindow}
*/
display: function() {
if (this.scriptName) {
var $widget = $(this.content).dialog('widget');
$widget.find('.morebits-dialog-scriptname').remove();
var scriptnamespan = document.createElement('span');
scriptnamespan.className = 'morebits-dialog-scriptname';
scriptnamespan.textContent = this.scriptName + ' \u00B7 '; // U+00B7 MIDDLE DOT = ·
$widget.find('.ui-dialog-title').prepend(scriptnamespan);
}
var dialog = $(this.content).dialog('open');
if (window.setupTooltips && window.pg && window.pg.re && window.pg.re.diff) { // tie in with NAVPOP
dialog.parent()[0].ranSetupTooltipsAlready = false;
window.setupTooltips(dialog.parent()[0]);
}
this.setHeight(this.height); // init height algorithm
return this;
},
/**
* Sets the dialog title.
* @param {string} title
* @returns {Morebits.simpleWindow}
*/
setTitle: function(title) {
$(this.content).dialog('option', 'title', title);
return this;
},
/**
* Sets the script name, appearing as a prefix to the title to help users determine which
* user script is producing which dialog. For instance, Twinkle modules set this to "Twinkle".
* @param {string} name
* @returns {Morebits.simpleWindow}
*/
setScriptName: function(name) {
this.scriptName = name;
return this;
},
/**
* Sets the dialog width.
* @param {number} width
* @returns {Morebits.simpleWindow}
*/
setWidth: function(width) {
$(this.content).dialog('option', 'width', width);
return this;
},
/**
* Sets the dialog's maximum height. The dialog will auto-size to fit its contents,
* but the content area will grow no larger than the height given here.
* @param {number} height
* @returns {Morebits.simpleWindow}
*/
setHeight: function(height) {
this.height = height;
// from display time onwards, let the browser determine the optimum height,
// and instead limit the height at the given value
// note that the given height will exclude the approx. 20px that the jQuery UI
// chrome has in height in addition to the height of an equivalent "classic"
// Morebits.simpleWindow
if (parseInt(getComputedStyle($(this.content).dialog('widget')[0], null).height, 10) > window.innerHeight) {
$(this.content).dialog('option', 'height', window.innerHeight - 2).dialog('option', 'position', 'top');
} else {
$(this.content).dialog('option', 'height', 'auto');
}
$(this.content).dialog('widget').find('.morebits-dialog-content')[0].style.maxHeight = parseInt(this.height - 30, 10) + 'px';
return this;
},
/**
* Sets the content of the dialog to the given element node, usually from rendering
* a Morebits.quickForm.
* Re-enumerates the footer buttons, but leaves the footer links as they are.
* Be sure to call this at least once before the dialog is displayed...
* @param {HTMLElement} content
* @returns {Morebits.simpleWindow}
*/
setContent: function(content) {
this.purgeContent();
this.addContent(content);
return this;
},
/**
* Adds the given element node to the dialog content.
* @param {HTMLElement} content
* @returns {Morebits.simpleWindow}
*/
addContent: function(content) {
this.content.appendChild(content);
// look for submit buttons in the content, hide them, and add a proxy button to the button pane
var thisproxy = this;
$(this.content).find('input[type="submit"], button[type="submit"]').each(function(key, value) {
value.style.display = 'none';
var button = document.createElement('button');
button.textContent = value.hasAttribute('value') ? value.getAttribute('value') : value.textContent ? value.textContent : 'ثبت درخواست'; // localized
button.className = value.className || 'submitButtonProxy';
// here is an instance of cheap coding, probably a memory-usage hit in using a closure here
button.addEventListener('click', function() {
value.click();
}, false);
thisproxy.buttons.push(button);
});
// remove all buttons from the button pane and re-add them
if (this.buttons.length > 0) {
$(this.content).dialog('widget').find('.morebits-dialog-buttons').empty().append(this.buttons)[0].removeAttribute('data-empty');
} else {
$(this.content).dialog('widget').find('.morebits-dialog-buttons')[0].setAttribute('data-empty', 'data-empty'); // used by CSS
}
return this;
},
/**
* Removes all contents from the dialog, barring any footer links
* @returns {Morebits.simpleWindow}
*/
purgeContent: function() {
this.buttons = [];
// delete all buttons in the buttonpane
$(this.content).dialog('widget').find('.morebits-dialog-buttons').empty();
while (this.content.hasChildNodes()) {
this.content.removeChild(this.content.firstChild);
}
return this;
},
/**
* Adds a link in the bottom-right corner of the dialog.
* This can be used to provide help or policy links.
* For example, Twinkle's CSD module adds a link to the CSD policy page,
* as well as a link to Twinkle's documentation.
* @param {string} text Link's text content
* @param {string} wikiPage Link target
* @param {boolean} [prep=false] Set true to prepend rather than append
* @returns {Morebits.simpleWindow}
*/
addFooterLink: function(text, wikiPage, prep) {
var $footerlinks = $(this.content).dialog('widget').find('.morebits-dialog-footerlinks');
if (this.hasFooterLinks) {
var bullet = document.createElement('span');
bullet.textContent = ' \u2022 '; // U+2022 BULLET
if (prep) {
$footerlinks.prepend(bullet);
} else {
$footerlinks.append(bullet);
}
}
var link = document.createElement('a');
link.setAttribute('href', mw.util.getUrl(wikiPage));
link.setAttribute('title', wikiPage);
link.setAttribute('target', '_blank');
link.textContent = text;
if (prep) {
$footerlinks.prepend(link);
} else {
$footerlinks.append(link);
}
this.hasFooterLinks = true;
return this;
},
/**
* Set whether the window should be modal or not.
* If set to true, other items on the page will be disabled, i.e., cannot be
* interacted with. Modal dialogs create an overlay below the dialog but above
* other page elements.
* This must be used (if necessary) before calling display()
* Default: false
* @param {boolean} modal
* @returns {Morebits.simpleWindow}
*/
setModality: function(modal) {
$(this.content).dialog('option', 'modal', modal);
return this;
}
};
/**
* Enables or disables all footer buttons on all Morebits.simpleWindows in the current page.
* This should be called with `false` when the button(s) become irrelevant (e.g. just before
* Morebits.status.init is called).
* This is not an instance method so that consumers don't have to keep a reference to the
* original Morebits.simpleWindow object sitting around somewhere. Anyway, most of the time
* there will only be one Morebits.simpleWindow open, so this shouldn't matter.
* @param {boolean} enabled
*/
Morebits.simpleWindow.setButtonsEnabled = function(enabled) {
$('.morebits-dialog-buttons button').prop('disabled', !enabled);
};
// Twinkle blacklist was removed per consensus at http://en.wikipedia.org/wiki/Wikipedia:Administrators%27_noticeboard/Archive221#New_Twinkle_blacklist_proposal
}(window, document, jQuery)); // End wrap with anonymous function
/**
* If this script is being executed outside a ResourceLoader context, we add some
* global assignments for legacy scripts, hopefully these can be removed down the line
*
* IMPORTANT NOTE:
* PLEASE DO NOT USE THESE ALIASES IN NEW CODE!
* Thanks.
*/
if (typeof arguments === 'undefined') { // typeof is here for a reason...
/* global Morebits */
window.SimpleWindow = Morebits.simpleWindow;
window.QuickForm = Morebits.quickForm;
window.Wikipedia = Morebits.wiki;
window.Status = Morebits.status;
}
// </nowiki>
/* ===== Twinkle.js ===== */
/**
* +-------------------------------------------------------------------------+
* | === WARNING: GLOBAL GADGET FILE === |
* | Changes to this page affect many users. |
* | Please discuss changes at [[WT:TW]] before editing. |
* +-------------------------------------------------------------------------+
*
* Imported from github [https://github.com/azatoth/twinkle].
* All changes should be made in the repository, otherwise they will be lost.
*
* ----------
*
* This is AzaToth's Twinkle, the popular script sidekick for newbies, admins, and
* every Wikipedian in between. Visit [[WP:TW]] for more information.
*/
// <nowiki>
/* global Morebits */
(function (window, document, $) { // Wrap with anonymous function
// Check if account is experienced enough to use Twinkle
if (!Morebits.userIsInGroup('autoconfirmed') && !Morebits.userIsInGroup('confirmed')) {
return;
}
var Twinkle = {};
window.Twinkle = Twinkle; // allow global access
// for use by custom modules (normally empty)
Twinkle.initCallbacks = [];
Twinkle.addInitCallback = function twinkleAddInitCallback(func) {
Twinkle.initCallbacks.push(func);
};
Twinkle.defaultConfig = {};
/**
* This holds the default set of preferences used by Twinkle.
* It is important that all new preferences added here, especially admin-only ones, are also added to
* |Twinkle.config.sections| in twinkleconfig.js, so they are configurable via the Twinkle preferences panel.
* For help on the actual preferences, see the comments in twinkleconfig.js.
*
* Formerly Twinkle.defaultConfig.twinkle and Twinkle.defaultConfig.friendly
*/
Twinkle.defaultConfig = {
// General
summaryAd: ' ([[ویکیواژه:توینکل|توینکل]])', // localized
deletionSummaryAd: ' ([[ویکیواژه:توینکل|توینکل]])', // localized
protectionSummaryAd: ' ([[ویکیواژه:توینکل|توینکل]])', // localized
userTalkPageMode: 'tab',
dialogLargeFont: false,
disabledModules: [],
disabledSysopModules: [],
// Block
defaultToPartialBlocks: false,
blankTalkpageOnIndefBlock: false,
// Fluff (revert and rollback)
autoMenuAfterRollback: false,
openTalkPage: [ 'agf', 'norm', 'vand' ],
openTalkPageOnAutoRevert: false,
rollbackInPlace: false,
markRevertedPagesAsMinor: [ 'vand' ],
watchRevertedPages: [ 'agf', 'norm', 'vand', 'torev' ],
offerReasonOnNormalRevert: true,
confirmOnFluff: false,
showRollbackLinks: [ 'diff', 'others' ],
// CSD
speedySelectionStyle: 'buttonClick',
watchSpeedyPages: [ "ع۳", "ع۵", "ع۱۰", "ع۱۱", "ع۱۲" ], // localized
markSpeedyPagesAsPatrolled: false,
// these next two should probably be identical by default
welcomeUserOnSpeedyDeletionNotification: [ "حذف سریع", "ع۱", "ع۲", "ع۳", "ع۴", "ع۶", "ع۱۰", "ع۱۱", "ع۱۲", "ع۱۳", "م۱", "م۲", "م۳", "م۵", "م۷", "م۹", "م۱۰", "م۱۱", "پ۱", "پ۲", "پ۳", "پ۷", "پ۹", "پ۱۰", "ک۳", "ک۵", "ت۲", "ت۳", "د۱", "د۲" ], // localized
notifyUserOnSpeedyDeletionNomination: [ "حذف سریع", "ع۱", "ع۲", "ع۳", "ع۴", "ع۶", "ع۱۰", "ع۱۱", "ع۱۲", "ع۱۳", "م۱", "م۲", "م۳", "م۵", "م۷", "م۹", "م۱۰", "م۱۱", "پ۱", "پ۲", "پ۳", "پ۷", "پ۹", "پ۱۰", "ک۳", "ک۵", "ت۲", "ت۳", "د۱", "د۲" ], // localized
warnUserOnSpeedyDelete: [ "حذف سریع", "ع۱", "ع۲", "ع۳", "ع۴", "ع۵", "ع۱۰", "ع۱۱", "ع۱۲", "م۱", "م۳", "م۷", "م۹", "م۱۰", "م۱۱", "پ۳", "پ۷", "پ۹", "ک۳", "ک۵", "ت۲", "د۱" ], // localized
promptForSpeedyDeletionSummary: [],
deleteTalkPageOnDelete: true,
deleteRedirectsOnDelete: true,
deleteSysopDefaultToDelete: false,
speedyWindowHeight: 500,
speedyWindowWidth: 800,
logSpeedyNominations: false,
speedyLogPageName: 'نامزدهای حذف سریع', // localized
noLogOnSpeedyNomination: [ 'ک۱' ], // localized
// Unlink
unlinkNamespaces: [ '0', '10', '100', '118' ],
// Warn
defaultWarningGroup: '1',
combinedSingletMenus: false,
showSharedIPNotice: true,
watchWarnings: true,
oldSelect: false,
customWarningList: [],
// Hidden preferences
revertMaxRevisions: 50,
autolevelStaleDays: 3, // Huggle is 3, CBNG is 2
batchMax: 5000,
batchdeleteChunks: 50,
batchProtectChunks: 50,
batchundeleteChunks: 50,
proddeleteChunks: 50,
// Formerly defaultConfig.friendly:
// Tag
groupByDefault: true,
watchTaggedPages: true,
watchMergeDiscussions: true,
markTaggedPagesAsMinor: false,
markTaggedPagesAsPatrolled: true,
tagArticleSortOrder: 'cat',
customTagList: [],
customFileTagList: [],
customRedirectTagList: [],
// Welcome
topWelcomes: false,
watchWelcomes: true,
welcomeHeading: 'Welcome',
insertHeadings: true,
insertUsername: true,
insertSignature: true, // sign welcome templates, where appropriate
quickWelcomeMode: 'norm',
quickWelcomeTemplate: 'welcome',
customWelcomeList: [],
customWelcomeSignature: true,
// Talkback
markTalkbackAsMinor: true,
insertTalkbackSignature: true, // always sign talkback templates
talkbackHeading: 'بازبحث ' + mw.config.get('wgUserName'), // localized
adminNoticeHeading: 'آگاهسازی', // localized
mailHeading: "نامه دارید!", // localized
// Shared
markSharedIPAsMinor: true
};
// now some skin dependent config.
switch (mw.config.get('skin')) {
case 'vector':
Twinkle.defaultConfig.portletArea = 'right-navigation';
Twinkle.defaultConfig.portletId = 'p-twinkle';
Twinkle.defaultConfig.portletName = 'توینکل'; // localized
Twinkle.defaultConfig.portletType = 'menu';
Twinkle.defaultConfig.portletNext = 'p-cactions'; // localized
break;
case 'timeless':
Twinkle.defaultConfig.portletArea = '#page-tools .sidebar-inner';
Twinkle.defaultConfig.portletId = 'p-twinkle';
Twinkle.defaultConfig.portletName = 'توینکل'; // localized
Twinkle.defaultConfig.portletType = null;
Twinkle.defaultConfig.portletNext = 'p-userpagetools';
break;
default:
Twinkle.defaultConfig.portletArea = null;
Twinkle.defaultConfig.portletId = 'p-cactions';
Twinkle.defaultConfig.portletName = null;
Twinkle.defaultConfig.portletType = null;
Twinkle.defaultConfig.portletNext = null;
}
Twinkle.getPref = function twinkleGetPref(name) {
if (typeof Twinkle.prefs === 'object' && Twinkle.prefs[name] !== undefined) {
return Twinkle.prefs[name];
}
// Old preferences format, used before twinkleoptions.js was a thing
if (typeof window.TwinkleConfig === 'object' && window.TwinkleConfig[name] !== undefined) {
return window.TwinkleConfig[name];
}
if (typeof window.FriendlyConfig === 'object' && window.FriendlyConfig[name] !== undefined) {
return window.FriendlyConfig[name];
}
return Twinkle.defaultConfig[name];
};
/**
* **************** Twinkle.addPortlet() ****************
*
* Adds a portlet menu to one of the navigation areas on the page.
* This is necessarily quite a hack since skins, navigation areas, and
* portlet menu types all work slightly different.
*
* Available navigation areas depend on the skin used.
* Vector:
* For each option, the outer div class contains "vector-menu", the inner div class is "vector-menu-content", and the ul is "vector-menu-content-list"
* "mw-panel", outer div class contains "vector-menu-portal". Existing portlets/elements: "p-logo", "p-navigation", "p-interaction", "p-tb", "p-coll-print_export"
* "left-navigation", outer div class contains "vector-menu-tabs" or "vector-menu-dropdown". Existing portlets: "p-namespaces", "p-variants" (menu)
* "right-navigation", outer div class contains "vector-menu-tabs" or "vector-menu-dropdown". Existing portlets: "p-views", "p-cactions" (menu), "p-search"
* Special layout of p-personal portlet (part of "head") through specialized styles.
* Monobook:
* "column-one", outer div class "portlet", inner div class "pBody". Existing portlets: "p-cactions", "p-personal", "p-logo", "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export"
* Special layout of p-cactions and p-personal through specialized styles.
* Modern:
* "mw_contentwrapper" (top nav), outer div class "portlet", inner div class "pBody". Existing portlets or elements: "p-cactions", "mw_content"
* "mw_portlets" (sidebar), outer div class "portlet", inner div class "pBody". Existing portlets: "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export"
*
* @param String navigation -- id of the target navigation area (skin dependant, on vector either of "left-navigation", "right-navigation", or "mw-panel")
* @param String id -- id of the portlet menu to create, preferably start with "p-".
* @param String text -- name of the portlet menu to create. Visibility depends on the class used.
* @param String type -- type of portlet. Currently only used for the vector non-sidebar portlets, pass "menu" to make this portlet a drop down menu.
* @param Node nextnodeid -- the id of the node before which the new item should be added, should be another item in the same list, or undefined to place it at the end.
*
* @return Node -- the DOM node of the new item (a DIV element) or null
*/
Twinkle.addPortlet = function(navigation, id, text, type, nextnodeid) {
// sanity checks, and get required DOM nodes
var root = document.getElementById(navigation) || document.querySelector(navigation);
if (!root) {
return null;
}
var item = document.getElementById(id);
if (item) {
if (item.parentNode && item.parentNode === root) {
return item;
}
return null;
}
var nextnode;
if (nextnodeid) {
nextnode = document.getElementById(nextnodeid);
}
// verify/normalize input
var skin = mw.config.get('skin');
if (skin !== 'vector' || (navigation !== 'left-navigation' && navigation !== 'right-navigation')) {
type = null; // menu supported only in vector's #left-navigation & #right-navigation
}
var outerDivClass, innerDivClass;
switch (skin) {
case 'vector':
// XXX: portal doesn't work
if (navigation !== 'portal' && navigation !== 'left-navigation' && navigation !== 'right-navigation') {
navigation = 'mw-panel';
}
outerDivClass = 'vector-menu vector-menu-' + (navigation === 'mw-panel' ? 'portal' : type === 'menu' ? 'dropdown' : 'tabs');
innerDivClass = 'vector-menu-content';
break;
case 'modern':
if (navigation !== 'mw_portlets' && navigation !== 'mw_contentwrapper') {
navigation = 'mw_portlets';
}
outerDivClass = 'portlet';
break;
case 'timeless':
outerDivClass = 'mw-portlet';
innerDivClass = 'mw-portlet-body';
break;
default:
navigation = 'column-one';
outerDivClass = 'portlet';
break;
}
// Build the DOM elements.
var outerDiv = document.createElement('nav');
outerDiv.setAttribute('aria-labelledby', id + '-label');
// Vector getting vector-menu-empty FIXME TODO
outerDiv.className = outerDivClass + ' emptyPortlet';
outerDiv.id = id;
if (nextnode && nextnode.parentNode === root) {
root.insertBefore(outerDiv, nextnode);
} else {
root.appendChild(outerDiv);
}
var h3 = document.createElement('h3');
h3.id = id + '-label';
var ul = document.createElement('ul');
if (skin === 'vector') {
// add invisible checkbox to keep menu open when clicked
// similar to the p-cactions ("More") menu
if (outerDivClass.indexOf('vector-menu-dropdown') !== -1) {
var chkbox = document.createElement('input');
chkbox.className = 'vectorMenuCheckbox vector-menu-checkbox'; // remove vectorMenuCheckbox after 1.35-wmf.37 goes live
chkbox.setAttribute('type', 'checkbox');
chkbox.setAttribute('aria-labelledby', id + '-label');
outerDiv.appendChild(chkbox);
var span = document.createElement('span');
span.appendChild(document.createTextNode(text));
h3.appendChild(span);
var a = document.createElement('a');
a.href = '#';
$(a).click(function(e) {
e.preventDefault();
});
h3.appendChild(a);
}
outerDiv.appendChild(h3);
ul.className = 'menu vector-menu-content-list'; // remove menu after 1.35-wmf.37 goes live
} else {
h3.appendChild(document.createTextNode(text));
outerDiv.appendChild(h3);
}
if (innerDivClass) {
var innerDiv = document.createElement('div');
innerDiv.className = innerDivClass;
innerDiv.appendChild(ul);
outerDiv.appendChild(innerDiv);
} else {
outerDiv.appendChild(ul);
}
return outerDiv;
};
/**
* **************** Twinkle.addPortletLink() ****************
* Builds a portlet menu if it doesn't exist yet, and add the portlet link.
* @param task: Either a URL for the portlet link or a function to execute.
*/
Twinkle.addPortletLink = function(task, text, id, tooltip) {
if (Twinkle.getPref('portletArea') !== null) {
Twinkle.addPortlet(Twinkle.getPref('portletArea'), Twinkle.getPref('portletId'), Twinkle.getPref('portletName'), Twinkle.getPref('portletType'), Twinkle.getPref('portletNext'));
}
var link = mw.util.addPortletLink(Twinkle.getPref('portletId'), typeof task === 'string' ? task : '#', text, id, tooltip);
$('.client-js .skin-vector #p-cactions').css('margin-right', 'initial');
if (typeof task === 'function') {
$(link).click(function (ev) {
task();
ev.preventDefault();
});
}
if ($.collapsibleTabs) {
$.collapsibleTabs.handleResize();
}
return link;
};
/**
* **************** General initialization code ****************
*/
var scriptpathbefore = mw.util.wikiScript('index') + '?title=',
scriptpathafter = '&action=raw&ctype=text/javascript&happy=yes';
// Retrieve the user's Twinkle preferences
$.ajax({
url: scriptpathbefore + 'User:' + encodeURIComponent(mw.config.get('wgUserName')) + '/twinkleoptions.js' + scriptpathafter,
dataType: 'text'
})
.fail(function () {
mw.notify('Could not load your Twinkle preferences', {type: 'error'});
})
.done(function (optionsText) {
// Quick pass if user has no options
if (optionsText === '') {
return;
}
// Twinkle options are basically a JSON object with some comments. Strip those:
optionsText = optionsText.replace(/(?:^(?:\/\/[^\n]*\n)*\n*|(?:\/\/[^\n]*(?:\n|$))*$)/g, '');
// First version of options had some boilerplate code to make it eval-able -- strip that too. This part may become obsolete down the line.
if (optionsText.lastIndexOf('window.Twinkle.prefs = ', 0) === 0) {
optionsText = optionsText.replace(/(?:^window.Twinkle.prefs = |;\n*$)/g, '');
}
try {
var options = JSON.parse(optionsText);
if (options) {
if (options.twinkle || options.friendly) { // Old preferences format
Twinkle.prefs = $.extend(options.twinkle, options.friendly);
} else {
Twinkle.prefs = options;
}
// v2 established after unification of Twinkle/Friendly objects
Twinkle.prefs.optionsVersion = Twinkle.prefs.optionsVersion || 1;
}
} catch (e) {
mw.notify('Could not parse your Twinkle preferences', {type: 'error'});
}
})
.always(function () {
$(Twinkle.load);
});
// Developers: you can import custom Twinkle modules here
// For example, mw.loader.load(scriptpathbefore + "User:UncleDouggie/morebits-test.js" + scriptpathafter);
Twinkle.load = function () {
// Don't activate on special pages other than those on the whitelist so that
// they load faster, especially the watchlist.
var specialPageWhitelist = [ 'Block', 'Contributions', 'Recentchanges', 'Recentchangeslinked' ]; // wgRelevantUserName defined for non-sysops on Special:Block
if (Morebits.userIsSysop) {
specialPageWhitelist = specialPageWhitelist.concat([ 'DeletedContributions', 'Prefixindex' ]);
}
if (mw.config.get('wgNamespaceNumber') === -1 &&
specialPageWhitelist.indexOf(mw.config.get('wgCanonicalSpecialPageName')) === -1) {
return;
}
// Prevent clickjacking
if (window.top !== window.self) {
return;
}
// Set custom Api-User-Agent header, for server-side logging purposes
Morebits.wiki.api.setApiUserAgent('Twinkle/2.0 (' + mw.config.get('wgDBname') + ')');
// Load all the modules in the order that the tabs should appear
var twinkleModules = [
// User/user talk-related
'warn', 'arv', 'block', 'welcome', 'shared', 'talkback',
// Deletion
'speedy', 'prod', 'deprod',
// Maintenance
'protect', 'tag',
// Misc. ones last
'diff', 'unlink', 'fluff', 'batchdelete', 'batchprotect', 'batchundelete'
];
// Don't load modules users have disabled
var disabledModules = Twinkle.getPref('disabledModules').concat(Twinkle.getPref('disabledSysopModules'));
twinkleModules.filter(function(mod) {
return disabledModules.indexOf(mod) === -1;
}).forEach(function(module) {
Twinkle[module]();
});
Twinkle.config.init(); // Can't turn off
// Run the initialization callbacks for any custom modules
Twinkle.initCallbacks.forEach(function (func) {
func();
});
Twinkle.addInitCallback = function (func) {
func();
};
// Increases text size in Twinkle dialogs, if so configured
if (Twinkle.getPref('dialogLargeFont')) {
mw.util.addCSS('.morebits-dialog-content, .morebits-dialog-footerlinks { font-size: 100% !important; } ' +
'.morebits-dialog input, .morebits-dialog select, .morebits-dialog-content button { font-size: inherit !important; }');
}
// Hide the lingering space if the TW menu is empty
if (mw.config.get('skin') === 'vector' && Twinkle.getPref('portletType') === 'menu' && $('#p-twinkle').length === 0) {
$('#p-cactions').css('margin-right', 'initial');
}
};
}(window, document, jQuery)); // End wrap with anonymous function
// </nowiki>
/* ===== twinkleconfig.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinkleconfig.js: Preferences module
****************************************
* Mode of invocation: Adds configuration form to Wikipedia:Twinkle/Preferences,
and adds an ad box to the top of user subpages belonging to the
currently logged-in user which end in '.js'
* Active on: What I just said. Yeah.
I, [[User:This, that and the other]], originally wrote this. If the code is misbehaving, or you have any
questions, don't hesitate to ask me. (This doesn't at all imply [[WP:OWN]]ership - it's just meant to
point you in the right direction.) -- TTO
*/
Twinkle.config = {};
Twinkle.config.watchlistEnums = { yes: 'به فهرست پیگیری اضافه کن', no: "به فهرست پیگیری اضافه نکن", 'default': 'از تنظیمات وبگاه تبعیت کن' }; // localized
Twinkle.config.commonSets = {
csdCriteria: {
'حذف سریع': 'حذف سریع',
'خرابکاری': 'خرابکاری',
'حذف شده': 'حذف شده',
'نقض حق تکثیر': 'نقض حق تکثیر',
'تکراری': 'تکراری',
'استفاده نشده': 'استفاده نشده',
'ویکیمدیا': 'ویکیمدیا',
'بحث یتیم': 'بحث یتیم'
},
csdCriteriaDisplayOrder: [
'حذف سریع',
'خرابکاری',
'حذف شده',
'نقض حق تکثیر',
'تکراری',
'استفاده نشده',
'ویکیمدیا',
'بحث یتیم'
],
csdCriteriaNotification: {
'حذف سریع': 'حذف سریع',
'خرابکاری': 'خرابکاری',
'حذف شده': 'حذف شده',
'نقض حق تکثیر': 'نقض حق تکثیر',
'تکراری': 'تکراری',
'استفاده نشده': 'استفاده نشده',
'ویکیمدیا': 'ویکیمدیا',
'بحث یتیم': 'بحث یتیم'
},
csdCriteriaNotificationDisplayOrder: [
'حذف سریع',
'خرابکاری',
'حذف شده',
'نقض حق تکثیر',
'تکراری',
'استفاده نشده',
'ویکیمدیا',
'بحث یتیم'
],
csdAndDICriteria: {
'حذف سریع': 'حذف سریع',
'خرابکاری': 'خرابکاری',
'حذف شده': 'حذف شده',
'نقض حق تکثیر': 'نقض حق تکثیر',
'تکراری': 'تکراری',
'استفاده نشده': 'استفاده نشده',
'ویکیمدیا': 'ویکیمدیا',
'بحث یتیم': 'بحث یتیم'
},
csdAndDICriteriaDisplayOrder: [
'حذف سریع',
'خرابکاری',
'حذف شده',
'نقض حق تکثیر',
'تکراری',
'استفاده نشده',
'ویکیمدیا',
'بحث یتیم'
],
namespacesNoSpecial: {
'0': 'مقاله', // localized
'1': 'بحث (مقاله)', // localized
'2': 'کاربر', // localized
'3': 'بحث کاربر', // localized
'4': 'ویکیواژه', // localized
'5': 'بحث ویکیواژه', // localized
'6': 'پرونده', // localized
'7': 'بحث پرونده', // localized
'8': 'مدیاویکی', // localized
'9': 'بحث مدیاویکی', // localized
'10': 'الگو', // localized
'11': 'بحث الگو', // localized
'12': 'راهنما', // localized
'13': 'بحث راهنما', // localized
'14': 'رده', // localized
'15': 'بحث رده', // localized
'100': 'درگاه', // localized
'101': 'بحث درگاه', // localized
'108': 'کتاب', // localized
'109': 'بحث کتاب', // localized
'118': 'پیشنویس', // localized
'119': 'بحث پیشنویس', // localized
// '710': 'TimedText', // localized
// '711': 'TimedText talk', // localized
'828': 'پودمان', // localized
'829': 'بحث پودمان' // localized
}
};
/**
* Section entry format:
*
* {
* title: <human-readable section title>,
* adminOnly: <true for admin-only sections>,
* hidden: <true for advanced preferences that rarely need to be changed - they can still be modified by manually editing twinkleoptions.js>,
* preferences: [
* {
* name: <TwinkleConfig property name>,
* label: <human-readable short description - used as a form label>,
* helptip: <(optional) human-readable text (using valid HTML) that complements the description, like limits, warnings, etc.>
* adminOnly: <true for admin-only preferences>,
* type: <string|boolean|integer|enum|set|customList> (customList stores an array of JSON objects { value, label }),
* enumValues: <for type = "enum": a JSON object where the keys are the internal names and the values are human-readable strings>,
* setValues: <for type = "set": a JSON object where the keys are the internal names and the values are human-readable strings>,
* setDisplayOrder: <(optional) for type = "set": an array containing the keys of setValues (as strings) in the order that they are displayed>,
* customListValueTitle: <for type = "customList": the heading for the left "value" column in the custom list editor>,
* customListLabelTitle: <for type = "customList": the heading for the right "label" column in the custom list editor>
* },
* . . .
* ]
* },
* . . .
*
*/
Twinkle.config.sections = [
{
title: 'عمومی', // localized
preferences: [
// TwinkleConfig.summaryAd (string)
// Text to be appended to the edit summary of edits made using Twinkle
{
name: 'summaryAd',
label: "پسوند تبلیغی جهت ضمیمهشدن به خلاصه ویرایشهای توینکل", // localized
helptip: 'خلاصهٔ پیشرفته باید با یک فاصله آغاز شده و تا حد امکان کوتاه نوشتهشود.', // localized (to Huji: Summary Ad را به خلاصهٔ پیشرفته ترجمه کردم، چون «تبلیغ در خلاصه ویرایش منطقی نبود؛ آیا خلاصهٔ پیشرفته ترجمهٔ دقیقی است؟)
type: 'string'
},
// TwinkleConfig.deletionSummaryAd (string)
// Text to be appended to the edit summary of deletions made using Twinkle
{
name: 'deletionSummaryAd',
label: 'خلاصهٔ مورد استفاده در خلاصه ویرایش حذف', // localized
helptip: 'معمولاً برابر با همان خلاصه ویرایش پیشرفته در بالا.', // localized
adminOnly: true,
type: 'string'
},
// TwinkleConfig.protectionSummaryAd (string)
// Text to be appended to the edit summary of page protections made using Twinkle
{
name: 'protectionSummaryAd',
label: 'خلاصهٔ پیشرفته برای استفاده در خلاصه ویرایش محافظت صفحه', // localized
helptip: 'معمولاً برابر با همان خلاصه ویرایش پیشرفته در بالا.', // localized
adminOnly: true,
type: 'string'
},
// TwinkleConfig.userTalkPageMode may take arguments:
// 'window': open a new window, remember the opened window
// 'tab': opens in a new tab, if possible.
// 'blank': force open in a new window, even if such a window exists
{
name: 'userTalkPageMode',
label: 'باز کردن بحث کاربر در ...', // localized
type: 'enum',
enumValues: { window: 'یک پنجره، بهجای بحث کاربران', tab: 'یک زبانهٔ جدید', blank: 'یک پنجرهٔ کاملاً جدید' } // localized
},
// TwinkleConfig.dialogLargeFont (boolean)
{
name: 'dialogLargeFont',
label: 'استفاده از متن بزرگتر در پیامهای توینکل', // localized
type: 'boolean'
},
// Twinkle.config.disabledModules (array)
{
name: 'disabledModules',
label: 'خاموشکردن پودمانهای انتخابشدهٔ توینکل', // localized
helptip: 'هر آنچه در اینجا انتخاب کنید، برای استفاده در دسترس نخواهد بود، پس با دقت عمل کنید. برای فعالکردن دوباره، گزینهها را غیرفعال کنید.', // localized
type: 'set',
setValues: { /*arv: 'ARV',*/ warn: 'هشدار', welcome: 'خوش آمدید', shared: 'نشانی آیپی مشترک', talkback: 'بازبحث', speedy: 'حذف سریع', prod: 'حذف زماندار', xfd: 'نظرخواهی حذف', image: 'تصویر', protect: 'محافظت (دمص)', tag: 'برچسب', diff: 'تفاوت', unlink: 'حذف پیوند', 'fluff': 'بازگردانی و واگردانی' } // localized
},
// Twinkle.config.disabledSysopModules (array)
{
name: 'disabledSysopModules',
label: 'خاموش کردن پودمانهای ویژهٔ مدیران', // localized
helptip: 'هر آنچه در اینجا انتخاب کنید، برای استفاده در دسترس نخواهد بود، پس با دقت عمل کنید. برای فعالکردن دوباره، گزینهها را غیرفعال کنید.', // localized
adminOnly: true,
type: 'set',
setValues: { block: 'بستن', deprod: 'مخالفت با حذف زماندار (مخزمان)', batchdelete: 'حذف دستهجمعی', batchprotect: 'محافظت دستهجمعی', batchundelete: 'احیای دستهجمعی' } // localized
}
]
},
/*
{
title: 'ARV',
preferences: [
{
name: 'spiWatchReport',
label: 'افزودن صفحههای گزارش زاپاسبازی به فهرست پیگیری', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
}
]
},
*/ // localized [removed]
{
title: 'بستن کاربر', // localized
adminOnly: true,
preferences: [
// TwinkleConfig.defaultToPartialBlocks (boolean)
// Whether to default partial blocks on or off
{
name: 'defaultToPartialBlocks',
label: 'به صورت پیشفرض در زمان بازکردن منوی بستن، قطع دسترسی موردی انتخاب شود', // localized
type: 'boolean'
},
// TwinkleConfig.blankTalkpageOnIndefBlock (boolean)
// if true, blank the talk page when issuing an indef block notice (per [[WP:UWUL#Indefinitely blocked users]])
{
name: 'blankTalkpageOnIndefBlock',
label: 'صفحه بحث را در زمان قطع دسترسی بیپایان پاک شود', // localized
helptip: 'برای اطلاعات بیشتر، <a href="' + mw.util.getUrl('en:Wikipedia:WikiProject_User_warnings/Usage_and_layout#Indefinitely_blocked_users') + '">WP:UWUL</a> را ببینید.', // localized
type: 'boolean'
}
]
},
{
title: 'حذف تصویر', // localized
preferences: [
// TwinkleConfig.notifyUserOnDeli (boolean)
// If the user should be notified after placing a file deletion tag
{
name: 'notifyUserOnDeli',
label: '"اطلاعرسانی به بارگذار پرونده" به صورت پیشفرض فعال باشد', // localized
type: 'boolean'
},
// TwinkleConfig.deliWatchPage (string)
// The watchlist setting of the page tagged for deletion. Either "yes", "no", or "default". Default is "default" (Duh).
{
name: 'deliWatchPage',
label: 'تصویر در زمان برچسبزدن به فهرست پیگیری اضافه شود', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
// TwinkleConfig.deliWatchUser (string)
// The watchlist setting of the user talk page if a notification is placed. Either "yes", "no", or "default". Default is "default" (Duh).
{
name: 'deliWatchUser',
label: 'صفحه بحث بارگذار پرونده در زمان اطلاعرسانی، به فهرست پیگیری اضافه شود', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
}
]
},
{
title: 'حذف زماندار', // localized
preferences: [
// TwinkleConfig.watchProdPages (boolean)
// If, when applying prod template to page, to watch the page
{
name: 'watchProdPages',
label: 'مقاله در زمان برچسبزدن به فهرست پیگیری اضافه شود', // localized
type: 'boolean'
},
// TwinkleConfig.markProdPagesAsPatrolled (boolean)
// If, when applying prod template to page, to mark the page as curated/patrolled (if the page was reached from NewPages)
{
name: 'markProdPagesAsPatrolled',
label: 'علامت زدن صفحه بهعنوان گشتخورده/بازبینیشده در زمان برچسب زدن (در صورت امکان)', // localized
helptip: 'از آنجا که این عمل بر خلاف اجماع برای بهترین راهکار است، در بیشتر موارد نباید این گزینه را انتخاب کرد', // localized
type: 'boolean'
},
// TwinkleConfig.prodReasonDefault (string)
// The prefilled PROD reason.
{
name: 'prodReasonDefault',
label: 'دلیل پیشفرض حذف زماندار', // localized
type: 'string'
},
{
name: 'logProdPages',
label: 'نگهداری سیاههای از همهٔ صفحههایی که نامزد حذف زماندار کردهاید در زیرصفحه کاربری', // localized
helptip: 'از آنجا که کاربران غیرمدیر به مشارکتهای حذفشدهٔ خود دسترسی ندارند، سیاههٔ نگهداریشده در زیرصفحهٔ کاربری میتواند برای پیگیری تمام صفحههایی که با استفاده از توینکل نامزد حذف زماندار کردهاید، مفید باشد.', // localized
type: 'boolean'
},
{
name: 'prodLogPageName',
label: 'نگهداری سیاههٔ حذف زماندار در این زیرصفحهٔ کاربری', // localized
helptip: 'نام یک زیرصفحه را در این جعبه وارد کنید. میتوانید در صفحهٔ کاربر:<i>نام کاربری</i>/<i>نام زیرصفحه</i> به سیاههٔ حذف زماندار خود دسترسی داشته باشید. این قابلیت تنها در صورتی کار میکند که گزینهٔ سیاههٔ حذف زماندار در زیرصفحه کاربری را فعال کرده باشید.', // localized
type: 'string'
}
]
},
{
title: 'برگرداندن و واگردانی', // twinklefluff module // localized
preferences: [
// TwinkleConfig.autoMenuAfterRollback (bool)
// Option to automatically open the warning menu if the user talk page is opened post-reversion
{
name: 'autoMenuAfterRollback',
label: 'باز کردن خودکار منوی هشدار توینکل در صفحهٔ بحث کاربر پس از بازگردانی با توینکل', // localized
helptip: 'تنها در صورتی کار میکند که گزینهٔ مرتبط در پایین را فعال کرده باشید.', // localized
type: 'boolean'
},
// TwinkleConfig.openTalkPage (array)
// What types of actions that should result in opening of talk page
{
name: 'openTalkPage',
label: 'باز کردن بحث کاربر پس از انجام این گونههای بازبینی', // localized
type: 'set',
setValues: { agf: 'برگردانی با فرض حسن نیت', norm: 'برگردانی عادی', vand: 'برگردانی خرابکاری', torev: '«برگرداندن این نسخه»' } // localized
},
// TwinkleConfig.openTalkPageOnAutoRevert (bool)
// Defines if talk page should be opened when calling revert from contribs or recent changes pages. If set to true, openTalkPage defines then if talk page will be opened.
{
name: 'openTalkPageOnAutoRevert',
label: 'باز کردن بحث کاربر بههنگام فراخواندن برگردانی از طریق مشارکتهای کاربر یا تغییرات اخیر', // localized
helptip: 'برای اینکه این قابلیت کار کند، در صورت فعال بودن این گزینه باید گزینههای مورد نظر در تنظیمات قبلی نیز فعال شده باشند.', // localized
type: 'boolean'
},
// TwinkleConfig.rollbackInPlace (bool)
//
{
name: 'rollbackInPlace',
label: "جلوگیری از بارگیری مجدد صفحه در زمان برگردانی از طریق مشارکتهای کاربر یا تغییرات اخیر", // localized
helptip: "در صورت فعال بودن این گزینه، توینکل صفحهٔ مشارکتهای کاربر یا تغییرات اخیر را پس از برگردانی مجدداً بارگیری نخواهد کرد. این به شما این امکان را میدهد که همزمان بیش از یک ویرایش را برگردانی کنید.", // localized
type: 'boolean'
},
// TwinkleConfig.markRevertedPagesAsMinor (array)
// What types of actions that should result in marking edit as minor
{
name: 'markRevertedPagesAsMinor',
label: 'علامتزدن این گونههای بازبینی بهعنوان ویرایش جزئی',
type: 'set',
setValues: { agf: 'برگردانی با فرض حسن نیت', norm: 'برگردانی عادی', vand: 'برگردانی خرابکاری', torev: '«برگرداندن این نسخه»' }
},
// TwinkleConfig.watchRevertedPages (array)
// What types of actions that should result in forced addition to watchlist
{
name: 'watchRevertedPages',
label: 'برای این انواع واگردانی، صفحه را به فهرست پیگیری بیفزا', // localized
type: 'set',
setValues: { agf: 'برگردانی با فرض حسن نیت', norm: 'برگردانی عادی', vand: 'برگردانی خرابکاری', torev: '«برگرداندن این نسخه»' } // localized
},
// TwinkleConfig.offerReasonOnNormalRevert (boolean)
// If to offer a prompt for extra summary reason for normal reverts, default to true
{
name: 'offerReasonOnNormalRevert',
label: 'درخواست دلیل برای برگردانیهای عادی', // localized
helptip: 'برگردانیهای «عادی» آنهایی هستند که از طریق پیوند [برگردانی] میانی فراخوانده شدهاند.', // localized
type: 'boolean'
},
{
name: 'confirmOnFluff',
label: 'قبل از بازگرداندن پیغام تایید نمایش داده شود', // localized
helptip: 'برای کاربرانی که از دستگاههای لمسی یا قلمی استفاده میکنند، و افرادی که بهطور مزمن، مردد هستند.', // localized
type: 'boolean'
},
// TwinkleConfig.showRollbackLinks (array)
// Where Twinkle should show rollback links:
// diff, others, mine, contribs, history, recent
// Note from TTO: |contribs| seems to be equal to |others| + |mine|, i.e. redundant, so I left it out heres
{
name: 'showRollbackLinks',
label: 'نمایش پیوندهای برگردانی در این صفحهها', // localized
type: 'set',
setValues: { diff: 'صفحههای تفاوت', others: 'صفحههای مشارکتهای کاربران دیگر', mine: 'صفحهٔ مشارکتهای من', recent: 'تغییرات اخیر و صفحههای ویژهٔ تغییرات مرتبط', history: 'صفحههای تاریخچه' } // localized
}
]
},
{
title: 'برچسب آیپی مشترک', // localized
preferences: [
{
name: 'markSharedIPAsMinor',
label: 'علامتگذاری برچسبزدن آیپی مشترک بهعنوان ویرایش جزئی', // localized
type: 'boolean'
}
]
},
{
title: 'حذف سریع', // localized
preferences: [
{
name: 'speedySelectionStyle',
label: 'چه زمانی صفحه حذف شود یا برچسب زده شود؟', // localized
type: 'enum',
enumValues: { 'buttonClick': 'وقتی روی دکمهٔ «ثبت» کلیک کردم', 'radioClick': 'به محض کلیک کردن روی گزینه' } // localized
},
// TwinkleConfig.watchSpeedyPages (array)
// Whether to add speedy tagged or deleted pages to watchlist
{
name: 'watchSpeedyPages',
label: 'برای این معیارها، صفحه را به فهرست پیگیری بیفزا', // localized
type: 'set',
setValues: Twinkle.config.commonSets.csdCriteria,
setDisplayOrder: Twinkle.config.commonSets.csdCriteriaDisplayOrder
},
// TwinkleConfig.markSpeedyPagesAsPatrolled (boolean)
// If, when applying speedy template to page, to mark the page as triaged/patrolled (if the page was reached from NewPages)
{
name: 'markSpeedyPagesAsPatrolled',
label: 'در زمان نصب برچسب، صفحه را در صورت امکان گشت بزن یا بازبینی کن', // localized
helptip: 'این گزینه احتمالاً نباید استفاده شود چون برای آن اجماع وجود ندارد', // localized
type: 'boolean'
},
// TwinkleConfig.welcomeUserOnSpeedyDeletionNotification (array of strings)
// On what types of speedy deletion notifications shall the user be welcomed
// with a "firstarticle" notice if their talk page has not yet been created.
{
name: 'welcomeUserOnSpeedyDeletionNotification',
label: 'وقتی کاربر را از برچسبهای مربوط به این معیارها آگاه میکنی به او خوشامد هم بگو', // localized
helptip: 'پیام خوشامد فقط وقتی افزوده میشود که صفحهٔ بحث کاربر وجود نداشته باشد. الگوی {{نخستین نوشتار}} برای این منظور به کار خواهد رفت.', // localized
type: 'set',
setValues: Twinkle.config.commonSets.csdCriteriaNotification,
setDisplayOrder: Twinkle.config.commonSets.csdCriteriaNotificationDisplayOrder
},
// TwinkleConfig.notifyUserOnSpeedyDeletionNomination (array)
// What types of actions should result in the author of the page being notified of nomination
{
name: 'notifyUserOnSpeedyDeletionNomination',
label: 'برای این معیارها، سازندهٔ صفحه را آگاه کن', // localized
helptip: 'وقتی گزینهٔ آگاه کردن سازندهٔ صفحه را انتخاب میکند، این آگاهسازی فقط برای معیارهای زیر رخ خواهد داد.', // localized
type: 'set',
setValues: Twinkle.config.commonSets.csdCriteriaNotification,
setDisplayOrder: Twinkle.config.commonSets.csdCriteriaNotificationDisplayOrder
},
// TwinkleConfig.warnUserOnSpeedyDelete (array)
// What types of actions should result in the author of the page being notified of speedy deletion (admin only)
{
name: 'warnUserOnSpeedyDelete',
label: 'آگاهسازی سازندهٔ صفحه در زمان حذف با این معیارها', // localized
helptip: 'حتی در صورتی که در صفحهٔ معیارهای حذف سریع آگاهسازی را فعال کنید، این آگاهسازی تنها برای معیارهایی که در اینجا انتخاب شدهاند انجام خواهد شد.', // localized
adminOnly: true,
type: 'set',
setValues: Twinkle.config.commonSets.csdCriteriaNotification,
setDisplayOrder: Twinkle.config.commonSets.csdCriteriaNotificationDisplayOrder
},
// TwinkleConfig.promptForSpeedyDeletionSummary (array of strings)
{
name: 'promptForSpeedyDeletionSummary',
label: 'فراهم کردن امکان ویرایش خلاصهٔ حذف در صورت حذف با این معیارها', // localized
adminOnly: true,
type: 'set',
setValues: Twinkle.config.commonSets.csdAndDICriteria,
setDisplayOrder: Twinkle.config.commonSets.csdAndDICriteriaDisplayOrder
},
// TwinkleConfig.deleteTalkPageOnDelete (boolean)
// If talk page if exists should also be deleted (CSD G8) when spedying a page (admin only)
{
name: 'deleteTalkPageOnDelete',
label: 'انتخاب گزینهٔ «صفحهٔ بحث نیز حذف شود» بهطور پیشفرض', // localized
adminOnly: true,
type: 'boolean'
},
{
name: 'deleteRedirectsOnDelete',
label: 'انتخاب گزینهٔ «تغییرمسیرها نیز حذف شوند» بهطور پیشفرض', // localized
adminOnly: true,
type: 'boolean'
},
// TwinkleConfig.deleteSysopDefaultToDelete (boolean)
// Make the CSD screen default to "delete" instead of "tag" (admin only)
{
name: 'deleteSysopDefaultToDelete',
label: 'فعالسازی پیشفرض حذف بیدرنگ بهجای قرار دادن برچسب حذف سریع', // localized
helptip: 'در صورت از پیش موجود بودن برچسب حذف سریع، جالت پیشفرض توینکل همیشه حالت «حذف» خواهد بود', // localized
adminOnly: true,
type: 'boolean'
},
// TwinkleConfig.speedyWindowWidth (integer)
// Defines the width of the Twinkle SD window in pixels
{
name: 'speedyWindowWidth',
label: 'عرض پنجرهٔ حذف سریع (به پیکسل)', // localized
type: 'integer'
},
// TwinkleConfig.speedyWindowWidth (integer)
// Defines the width of the Twinkle SD window in pixels
{
name: 'speedyWindowHeight',
label: 'ارتفاع پنجرهٔ حذف سریع (به پیکسل)', // localized
helptip: 'اگر یک نمایشگر بزرگ دارید، ممکن است بخواهید این اندازه را افزایش دهید.', // localized
type: 'integer'
},
{
name: 'logSpeedyNominations',
label: 'نگهداری یک سیاهه از نامزدیها برای حذف سریع در زیرصفحهٔ کاربری', // localized
helptip: 'از آنجا که کاربران غیرمدیر به مشارکتهای حذفشدهٔ خود دسترسی ندارند، سیاههٔ نگهداریشده در زیرصفحهٔ کاربری میتواند برای پیگیری تمام صفحههایی که با استفاده از توینکل نامزد حذف سریع کردهاید، مفید باشد. پروندههایی که با معیارهای حذف پرونده برچسب خوردهاند نیز به این سیاهه افزوده میشوند.', // localized
type: 'boolean'
},
{
name: 'speedyLogPageName',
label: 'نگهداری سیاههٔ حذف سریع در این زیرصفحهٔ کاربری', // localized
helptip: 'نام یک زیرصفحه را در این جعبه وارد کنید. میتوانید در صفحهٔ کاربر:<i>نام کربری</i>/<i>نام زیرصفحه</i> به سیاههٔ حذف سریع خود دسترسی داشته باشید. این قابلیت تنها در صورتی کار میکند که گزینهٔ سیاههٔ حذف سریع در زیرصفحه کاربری را فعال کرده باشید.', // localized
type: 'string'
},
{
name: 'noLogOnSpeedyNomination',
label: 'عدم افزودن صفحه به سیاههٔ حذف در فضای کاربری در زمان استفاده از این معیارها',
type: 'set',
setValues: Twinkle.config.commonSets.csdAndDICriteria,
setDisplayOrder: Twinkle.config.commonSets.csdAndDICriteriaDisplayOrder
}
]
},
{
title: 'برچسب', // localized
preferences: [
{
name: 'watchTaggedPages',
label: 'در زمان افزودن برچسب، صفحه را به فهرست پیگیری بیفزا', // localized
type: 'boolean'
},
{
name: 'watchMergeDiscussions',
label: 'افزودن صفحههای بحث به فهرست پیگیری در زمان آغاز نظرخواهی برای ادغام', // localized
type: 'boolean'
},
{
name: 'markTaggedPagesAsMinor',
label: 'علامتزدن ویرایشها برای افزودن برچسب بهعنوان ویرایش جزئی', // localized
type: 'boolean'
},
{
name: 'markTaggedPagesAsPatrolled',
label: 'انتخاب گزینهٔ «علامتگذاری صفحه بهعنوان گشتخورده/بازبینیشده» بهصورت پیشفرض', // localized
type: 'boolean'
},
{
name: 'groupByDefault',
label: 'انتخاب گزینهٔ «گروهبندی در {{مشکلات متعدد}}» بهصورت پیشفرض', // localized
type: 'boolean'
},
{
name: 'tagArticleSortOrder',
label: 'حالت پیشفرض برای مرتبسازی برچسبهای مقالهها', // localized
type: 'enum',
enumValues: { 'cat': 'بر پایه رده', 'alpha': 'به ترتیب الفبا' } // localized
},
{
name: 'customTagList',
label: 'برچسبهای نگهداری سفارشی مقاله/پیشنویس جهت نمایش', // localized
helptip: "این برچسبها بهعنوان گزینههای بیشتر در پایین فهرست برچسبها نمایش مییابند. بهعنوان مثال، میتوانید برچسبهای نگهداری اضافهای که هنوز به برچسبهای پیشفرض توینکل اضافه نشدهاند را بیافزایید.", // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در کادر برچسب' // localized
},
{
name: 'customFileTagList',
label: 'برچسبهای سفارشی نگهداری پرونده جهت نمایش', // localized
helptip: 'برچسبهای بیشتری که میخواهید برای پروندهها بیافزایید.', // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در کادر برچسب' // localized
},
{
name: 'customRedirectTagList',
label: 'برچسبهای سفارشی ردهٔ تغییرمسیر جهت نمایش', // localized
helptip: 'برچسبهای بیشتری که میخواهید برای تغییرمسیرها بیافزایید.', // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در کادر برچسب' // localized
}
]
},
{
title: 'گفتگو', // localized
preferences: [
{
name: 'markTalkbackAsMinor',
label: 'علامتگذاری بازبحثها بهعنوان ویرایش جزئی', // localized
type: 'boolean'
},
{
name: 'insertTalkbackSignature',
label: 'قرار دادن امضا در درون بازبحثها', // localized
type: 'boolean'
},
{
name: 'talkbackHeading',
label: 'عنوان بخش برای استفاده در بازبحثها', // localized
type: 'string'
},
{
name: 'adminNoticeHeading',
label: "عنوان بخش برای استفاده در گزارشهای ارسالشده در تابلوی اعلانات مدیران", // localized
helptip: 'تنها مرتبط با وو:تام.', // localized
type: 'string'
},
{
name: 'mailHeading',
label: "عنوان بخش برای استفاده در پیامهای \«ایمیل دارید\»", // localized
type: 'string'
}
]
},
{
title: 'زدودن پیوند', // localized
preferences: [
// TwinkleConfig.unlinkNamespaces (array)
// In what namespaces unlink should happen, default in 0 (article), 10 (template), 100 (portal), and 118 (draft)
{
name: 'unlinkNamespaces',
label: 'پیوندها را از صفحههایی که در این فضاهای نام هستند بزدا', // localized
helptip: 'از انتخاب فضاهای بحث پرهیز کنید، چون توینکل ممکن است پیوند به بایگانیهای بحث را حذف کند که کاری است به شدت نکوهش شده.', // localized
type: 'set',
setValues: Twinkle.config.commonSets.namespacesNoSpecial
}
]
},
{
title: 'هشدار به کاربر', // localized
preferences: [
// TwinkleConfig.defaultWarningGroup (int)
// Which level warning should be the default selected group, default is 1
{
name: 'defaultWarningGroup',
label: 'سطح پیشفرض هشدارها', // localized
type: 'enum',
enumValues: {
'1': 'سطح ۱', // localized
'2': 'سطح ۲', // localized
'3': 'سطح ۳', // localized
'4': 'سطح ۴', // localized
'5': 'سطح ۵ (فوری)', // localized
'6': 'اعلان راجع به یک مشکل واحد', // localized
'7': 'هشدار راجع به یک مشکل واحد', // localized
// 8 was used for block templates before #260
'9': 'هشدار سفارشی', // localized
'10': 'تمام الگوهای هشدار', // localized
'11': 'انتخاب خودکار (سطح ۱-۴)' // localized
}
},
// TwinkleConfig.combinedSingletMenus (boolean)
// if true, show one menu with both single-issue notices and warnings instead of two separately
{
name: 'combinedSingletMenus',
label: 'جایگزینی دو منوی جداگانه برای تکاشکال در یک منوی ترکیبشده', // localized
helptip: 'انتخاب اعلانهای تکاشکال یا هشدارهای تکاشکال بهعنوان گزینهٔ پیشفرض شما، در صورت فعال بودن، این را به انتخاب پیشفرض شما تبدیل خواهد کرد.', // localized
type: 'boolean'
},
// TwinkleConfig.showSharedIPNotice may take arguments:
// true: to show shared ip notice if an IP address
// false: to not print the notice
{
name: 'showSharedIPNotice',
label: 'افزودن اعلان اضافی در صفحههای بحث آیپی مشترک', // localized
helptip: 'اعلان مورد استفاده، {{آگاهی آیپی مشترک}} است', // localized
type: 'boolean'
},
// TwinkleConfig.watchWarnings (boolean)
// if true, watch the page which has been dispatched an warning or notice, if false, default applies
{
name: 'watchWarnings',
label: 'افزودن بحث کاربری به فهرست پیگیری در زمان ارسال اعلان', // localized
type: 'boolean'
},
// TwinkleConfig.oldSelect (boolean)
// if true, use the native select menu rather the select2-based one
{
name: 'oldSelect',
label: 'استفاده از منوی انتخاب کلاسیک غیر قابل جستجو', // localized
type: 'boolean'
},
{
name: 'customWarningList',
label: 'الگوهای هشدار سفارشی برای نمایش', // localized
helptip: 'میتوانید الگوهای شخصی یا زیرصفحههای کاربری را بیافزایید. هشدارهای سفارشی در ردهٔ «هشدارهای سفارشی» در داخل کادر جعبهٔ هشدار نمایش مییابند.', // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در فهرست هشدار (برای خلاصه ویرایش نیز استفاده میشود)' // localized
}
]
},
{
title: 'خوشامدگویی', // localized
preferences: [
{
name: 'topWelcomes',
label: 'قرار دادن خوشامدهای در بالای محتوای موجود در صفحههای بحث کاربران', // localized
type: 'boolean'
},
{
name: 'watchWelcomes',
label: 'افزودن صفحههای بحث کاربران به فهرست پیگیری در زمان خوشامدگویی', // localized
helptip: 'انجام این کار، تجربهٔ شخصی شما از خوشامدگویی به یک کاربر را بهبود میبخشد - شما قادر خواهید بود تا واکنش کاربر بهعنوان یک تازهکار را ببینید و احتمالاً به او کمک کنید.', // localized
type: 'boolean'
},
{
name: 'insertUsername',
label: 'افزودن نام کربری شما به الگو (در صورت امکان)', // localized
helptip: "برخی الگوهای خوشامد دارای جملهٔ اولیهای مانند \«سلام، من <username> هستم. خوش آمدید\" یا مشابه آن هستند. اگر این گزینه را غیرفعال کنید، این الگوها نام کاربری شما را بهاین نحو نمایش نخواهند داد.", // localized
type: 'boolean'
},
{
name: 'quickWelcomeMode',
label: 'عملکرد پیوند «خوشامد» در صفحهٔ تفاوت', // localized
helptip: 'اگر حالت خوشامدگویی خودکار را انتخاب کنید، الگویی که در زیر مشخص کردهاید مورد استفاده قرار خواهد گرفت.', // localized
type: 'enum',
enumValues: { auto: 'خوشامدگویی خودکار', norm: 'الزام برای انتخاب یک الگو' } // localized
},
{
name: 'quickWelcomeTemplate',
label: 'الگوی مورد استفاده در زمان خوشامدگویی خودکار', // localized
helptip: 'نام یک الگوی خواشمد را بدون استفاده از آکولاد وارد کنید. پیوندی به مقالهٔ مورد نظر اضافه خواهد شد.', // localized
type: 'string'
},
{
name: 'customWelcomeList',
label: 'الگوهای خوشامد سفارشی برای نمایش', // localized
helptip: "میتوانید الگوهای خوشامد یا زیرصفحههای کاربری که الگوی خوشامدگویی هستند را اضافه کنید (همراه با پیشوند \"کاربر:\"). فراموش نکنید که این الگوها در صفحههای بحث کاربران جانشانی خواهند شد.", // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در کادر خوشامد' // localized
},
{
name: 'customWelcomeSignature',
label: 'امضای خودکار الگوهای خوشامد سفارشی', // localized
helptip: 'در صورتی که الگوهای خوشامد سفارشی شما شامل امضای یکپارچه در داخل الگو هستند، این گزینه را غیرفعال کنید.', // localized
type: 'boolean'
}
]
},
{
title: 'نظرخواهی برای حذف', // localized
preferences: [
{
name: 'logXfdNominations',
label: 'نگهداری یک سیاهه از نامزدیها در نظرخواهی برای حذف (نبح) توسط شما در زیرصفحهٔ کاربری', // localized
helptip: 'سیاههٔ نگهداریشده در زیرصفحهٔ کاربری میتواند برای پیگیری تمام صفحههایی که با استفاده از توینکل نامزد نظرخواهی برای حذف کردهاید، مفید باشد.', // localized
type: 'boolean'
},
{
name: 'xfdLogPageName',
label: 'نگهداری سیاههٔ نظرخواهی برای حذف در این زیرصفحهٔ کاربری', // localized
helptip: 'نام یک زیرصفحه را در این جعبه وارد کنید. میتوانید در صفحهٔ کاربر:<i>نام کربری</i>/<i>نام زیرصفحه</i> به سیاههٔ نظرخواهی برای حذف خود دسترسی داشته باشید. این قابلیت تنها در صورتی کار میکند که گزینهٔ سیاههٔ نظرخواهی برای حذف در زیرصفحه کاربری را فعال کرده باشید.', // localized
type: 'string'
},
{
name: 'noLogOnXfdNomination',
label: 'برای نامزدیهای حذف در این دسته، سیاههای در زیرصفحهٔ کاربریام ایجاد نکن', // localized
type: 'set',
setValues: { afd: 'نبح'/*, tfd: 'TfD', ffd: 'FfD', cfd: 'CfD', cfds: 'CfD/S', mfd: 'MfD', rfd: 'RfD', rm: 'RM'*/ } // localized
},
// TwinkleConfig.xfdWatchPage (string)
// The watchlist setting of the page being nominated for XfD. Either "yes" (add to watchlist), "no" (don't
// add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
{
name: 'xfdWatchPage',
label: 'صفحهٔ نامزد حذف را به فهرست پیگیری اضافه کن', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
// TwinkleConfig.xfdWatchDiscussion (string)
// The watchlist setting of the newly created XfD page (for those processes that create discussion pages for each nomination),
// or the list page for the other processes.
// Either "yes" (add to watchlist), "no" (don't add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
{
name: 'xfdWatchDiscussion',
label: 'صفحهٔ نظرخواهی را به فهرست پیگیری اضافه کن', // localized
helptip: 'این گزینه مربوط به صفحهای است که در آن بحث راجع به حذف صورت میگیرد، یعنی صفحهٔ نظرخواهی', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
// TwinkleConfig.xfdWatchList (string)
// The watchlist setting of the XfD list page, *if* the discussion is on a separate page. Either "yes" (add to watchlist), "no" (don't
// add to watchlist), or "default" (use setting from preferences). Default is "no" (Hehe. Seriously though, who wants to watch it?
// Sorry in advance for any false positives.).
/*
{
name: 'xfdWatchList',
label: 'افزودن صفحهٔ سیاهه/فهرست روزانه به فهرست پیگیری (در صورت امکان)', // localized
helptip: 'این گزینه تنها در مورد وو:نبح اعمال خواهد شد که بحثهای آن در یک سیاههٔ روزانه یا صفحهٔ اصلی نبح تراگنجایش میشوند.', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
*/ // localized [removed]
// TwinkleConfig.xfdWatchUser (string)
// The watchlist setting of the user talk page if they receive a notification. Either "yes" (add to watchlist), "no" (don't
// add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
{
name: 'xfdWatchUser',
label: 'صفحه بحث کاربر یا سازندهٔ اولیهٔ صفحه را در هنگام اطلاعرسانی به فهرست پیگیری اضافه کن', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
// TwinkleConfig.xfdWatchRelated (string)
// The watchlist setting of the target of a redirect being nominated for RfD. Either "yes" (add to watchlist), "no" (don't
// add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
{
name: 'xfdWatchRelated',
label: "در زمان اطلاعرسانی، صفحه هدف تغییرمسیر را به فهرست پیگیری اضافه کن", // localized
helptip: 'این فقط برای زمانی کاربرد دارد که شما در صفحه بحث هدف تغییرمسیر اطلاعرسانی کردهاید.', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
{
name: 'markXfdPagesAsPatrolled',
label: 'در صورت امکان صفحه را در زمان ایجاد نظرخواهی برای حذف گشت بزن', // localized
type: 'boolean'
}
]
},
{
title: 'پنهان', // localized
hidden: true,
preferences: [
// twinkle.js: portlet setup
{
name: 'portletArea',
type: 'string'
},
{
name: 'portletId',
type: 'string'
},
{
name: 'portletName',
type: 'string'
},
{
name: 'portletType',
type: 'string'
},
{
name: 'portletNext',
type: 'string'
},
// twinklefluff.js: defines how many revision to query maximum, maximum possible is 50, default is 50
{
name: 'revertMaxRevisions',
type: 'integer'
},
// twinklewarn.js: When using the autolevel select option, how many days makes a prior warning stale
// Huggle is three days ([[Special:Diff/918980316]] and [[Special:Diff/919417999]]) while ClueBotNG is two:
// https://github.com/DamianZaremba/cluebotng/blob/4958e25d6874cba01c75f11debd2e511fd5a2ce5/bot/action_functions.php#L62
{
name: 'autolevelStaleDays',
type: 'integer'
},
// twinklebatchdelete.js: How many pages should be processed maximum
{
name: 'batchMax',
type: 'integer',
adminOnly: true
},
// twinklebatchdelete.js: How many pages should be processed at a time
{
name: 'batchdeleteChunks',
type: 'integer',
adminOnly: true
},
// twinklebatchprotect.js: How many pages should be processed at a time
{
name: 'batchProtectChunks',
type: 'integer',
adminOnly: true
},
// twinklebatchundelete.js: How many pages should be processed at a time
{
name: 'batchundeleteChunks',
type: 'integer',
adminOnly: true
},
// twinkledeprod.js: How many pages should be processed at a time
{
name: 'proddeleteChunks',
type: 'integer',
adminOnly: true
}
]
}
]; // end of Twinkle.config.sections
Twinkle.config.init = function twinkleconfigInit() {
// create the config page at Wikipedia:Twinkle/Preferences
if ((mw.config.get('wgNamespaceNumber') === mw.config.get('wgNamespaceIds').project && mw.config.get('wgTitle') === 'توینکل/ترجیحات') &&
mw.config.get('wgAction') === 'view') {
if (!document.getElementById('twinkle-config')) {
return; // maybe the page is misconfigured, or something - but any attempt to modify it will be pointless
}
// set style (the url() CSS function doesn't seem to work from wikicode - ?!)
document.getElementById('twinkle-config-titlebar').style.backgroundImage = 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB%2FqqA%2BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEhQTFRFr73ZobTPusjdsMHZp7nVwtDhzNbnwM3fu8jdq7vUt8nbxtDkw9DhpbfSvMrfssPZqLvVztbno7bRrr7W1d%2Fs1N7qydXk0NjpkW7Q%2BgAAADVJREFUeNoMwgESQCAAAMGLkEIi%2FP%2BnbnbpdB59app5Vdg0sXAoMZCpGoFbK6ciuy6FX4ABAEyoAef0BXOXAAAAAElFTkSuQmCC)';
var contentdiv = document.getElementById('twinkle-config-content');
contentdiv.textContent = ''; // clear children
// let user know about possible conflict with skin js/common.js file
// (settings in that file will still work, but they will be overwritten by twinkleoptions.js settings)
if (window.TwinkleConfig || window.FriendlyConfig) {
var contentnotice = document.createElement('p');
contentnotice.innerHTML = '<table class="plainlinks ombox ombox-content"><tr><td class="mbox-image">' +
'<img alt="" src="https://upload.wikimedia.org/wikipedia/commons/3/38/Imbox_content.png" /></td>' +
'<td class="mbox-text"><p><big><b>قبل از ویرایش تنظیمات در اینجا،</b> شما باید تنظیمات قبلی توینکل یا تنظیمات سفارشی تم خود را حذف کنید.</big></p>' + // localized
'<p>برای انجام آن شما میتوانید <a href="' + mw.util.getUrl('User:' + mw.config.get('wgUserName') + '/' + mw.config.get('skin') + // localized
'.js', { action: 'edit' }) + '" target="_blank"><b>فایل جاوااسکریپت شخصی خود</b></a> یا <a href="' + // localized
mw.util.getUrl('User:' + mw.config.get('wgUserName') + '/common.js', { action: 'edit'}) + '" target="_blank"><b>فایل common.js خود را ویرایش کرده و</b></a> همه خطهای کد که به <code>TwinkleConfig</code> و <code>FriendlyConfig</code> مرتبط است را حذف کنید.</p>' + // localized
'</td></tr></table>';
contentdiv.appendChild(contentnotice);
}
// start a table of contents
var toctable = document.createElement('div');
toctable.className = 'toc';
toctable.style.marginLeft = '0.4em';
// create TOC title
var toctitle = document.createElement('div');
toctitle.id = 'toctitle';
var toch2 = document.createElement('h2');
toch2.textContent = 'Contents ';
toctitle.appendChild(toch2);
// add TOC show/hide link
var toctoggle = document.createElement('span');
toctoggle.className = 'toctoggle';
toctoggle.appendChild(document.createTextNode('['));
var toctogglelink = document.createElement('a');
toctogglelink.className = 'internal';
toctogglelink.setAttribute('href', '#tw-tocshowhide');
toctogglelink.textContent = 'hide';
toctoggle.appendChild(toctogglelink);
toctoggle.appendChild(document.createTextNode(']'));
toctitle.appendChild(toctoggle);
toctable.appendChild(toctitle);
// create item container: this is what we add stuff to
var tocul = document.createElement('ul');
toctogglelink.addEventListener('click', function twinkleconfigTocToggle() {
var $tocul = $(tocul);
$tocul.toggle();
if ($tocul.find(':visible').length) {
toctogglelink.textContent = 'hide';
} else {
toctogglelink.textContent = 'show';
}
}, false);
toctable.appendChild(tocul);
contentdiv.appendChild(toctable);
var tocnumber = 1;
var contentform = document.createElement('form');
contentform.setAttribute('action', 'javascript:void(0)'); // was #tw-save - changed to void(0) to work around Chrome issue
contentform.addEventListener('submit', Twinkle.config.save, true);
contentdiv.appendChild(contentform);
var container = document.createElement('table');
container.style.width = '100%';
contentform.appendChild(container);
$(Twinkle.config.sections).each(function(sectionkey, section) {
if (section.hidden || (section.adminOnly && !Morebits.userIsSysop)) {
return true; // i.e. "continue" in this context
}
// add to TOC
var tocli = document.createElement('li');
tocli.className = 'toclevel-1';
var toca = document.createElement('a');
toca.setAttribute('href', '#twinkle-config-section-' + tocnumber.toString());
toca.appendChild(document.createTextNode(section.title));
tocli.appendChild(toca);
tocul.appendChild(tocli);
var row = document.createElement('tr');
var cell = document.createElement('td');
cell.setAttribute('colspan', '3');
var heading = document.createElement('h4');
heading.style.borderBottom = '1px solid gray';
heading.style.marginTop = '0.2em';
heading.id = 'twinkle-config-section-' + (tocnumber++).toString();
heading.appendChild(document.createTextNode(section.title));
cell.appendChild(heading);
row.appendChild(cell);
container.appendChild(row);
var rowcount = 1; // for row banding
// add each of the preferences to the form
$(section.preferences).each(function(prefkey, pref) {
if (pref.adminOnly && !Morebits.userIsSysop) {
return true; // i.e. "continue" in this context
}
row = document.createElement('tr');
row.style.marginBottom = '0.2em';
// create odd row banding
if (rowcount++ % 2 === 0) {
row.style.backgroundColor = 'rgba(128, 128, 128, 0.1)';
}
cell = document.createElement('td');
var label, input;
switch (pref.type) {
case 'boolean': // create a checkbox
cell.setAttribute('colspan', '2');
label = document.createElement('label');
input = document.createElement('input');
input.setAttribute('type', 'checkbox');
input.setAttribute('id', pref.name);
input.setAttribute('name', pref.name);
if (Twinkle.getPref(pref.name) === true) {
input.setAttribute('checked', 'checked');
}
label.appendChild(input);
label.appendChild(document.createTextNode(' ' + pref.label));
cell.appendChild(label);
break;
case 'string': // create an input box
case 'integer':
// add label to first column
cell.style.textAlign = 'right';
cell.style.paddingRight = '0.5em';
label = document.createElement('label');
label.setAttribute('for', pref.name);
label.appendChild(document.createTextNode(pref.label + ':'));
cell.appendChild(label);
row.appendChild(cell);
// add input box to second column
cell = document.createElement('td');
cell.style.paddingRight = '1em';
input = document.createElement('input');
input.setAttribute('type', 'text');
input.setAttribute('id', pref.name);
input.setAttribute('name', pref.name);
if (pref.type === 'integer') {
input.setAttribute('size', 6);
input.setAttribute('type', 'number');
input.setAttribute('step', '1'); // integers only
}
if (Twinkle.getPref(pref.name)) {
input.setAttribute('value', Twinkle.getPref(pref.name));
}
cell.appendChild(input);
break;
case 'enum': // create a combo box
// add label to first column
// note: duplicates the code above, under string/integer
cell.style.textAlign = 'right';
cell.style.paddingRight = '0.5em';
label = document.createElement('label');
label.setAttribute('for', pref.name);
label.appendChild(document.createTextNode(pref.label + ':'));
cell.appendChild(label);
row.appendChild(cell);
// add input box to second column
cell = document.createElement('td');
cell.style.paddingRight = '1em';
input = document.createElement('select');
input.setAttribute('id', pref.name);
input.setAttribute('name', pref.name);
$.each(pref.enumValues, function(enumvalue, enumdisplay) {
var option = document.createElement('option');
option.setAttribute('value', enumvalue);
if (Twinkle.getPref(pref.name) === enumvalue) {
option.setAttribute('selected', 'selected');
}
option.appendChild(document.createTextNode(enumdisplay));
input.appendChild(option);
});
cell.appendChild(input);
break;
case 'set': // create a set of check boxes
// add label first of all
cell.setAttribute('colspan', '2');
label = document.createElement('label'); // not really necessary to use a label element here, but we do it for consistency of styling
label.appendChild(document.createTextNode(pref.label + ':'));
cell.appendChild(label);
var checkdiv = document.createElement('div');
checkdiv.style.paddingLeft = '1em';
var worker = function(itemkey, itemvalue) {
var checklabel = document.createElement('label');
checklabel.style.marginRight = '0.7em';
checklabel.style.display = 'inline-block';
var check = document.createElement('input');
check.setAttribute('type', 'checkbox');
check.setAttribute('id', pref.name + '_' + itemkey);
check.setAttribute('name', pref.name + '_' + itemkey);
if (Twinkle.getPref(pref.name) && Twinkle.getPref(pref.name).indexOf(itemkey) !== -1) {
check.setAttribute('checked', 'checked');
}
// cater for legacy integer array values for unlinkNamespaces (this can be removed a few years down the track...)
if (pref.name === 'unlinkNamespaces') {
if (Twinkle.getPref(pref.name) && Twinkle.getPref(pref.name).indexOf(parseInt(itemkey, 10)) !== -1) {
check.setAttribute('checked', 'checked');
}
}
checklabel.appendChild(check);
checklabel.appendChild(document.createTextNode(itemvalue));
checkdiv.appendChild(checklabel);
};
if (pref.setDisplayOrder) {
// add check boxes according to the given display order
$.each(pref.setDisplayOrder, function(itemkey, item) {
worker(item, pref.setValues[item]);
});
} else {
// add check boxes according to the order it gets fed to us (probably strict alphabetical)
$.each(pref.setValues, worker);
}
cell.appendChild(checkdiv);
break;
case 'customList':
// add label to first column
cell.style.textAlign = 'right';
cell.style.paddingRight = '0.5em';
label = document.createElement('label');
label.setAttribute('for', pref.name);
label.appendChild(document.createTextNode(pref.label + ':'));
cell.appendChild(label);
row.appendChild(cell);
// add button to second column
cell = document.createElement('td');
cell.style.paddingRight = '1em';
var button = document.createElement('button');
button.setAttribute('id', pref.name);
button.setAttribute('name', pref.name);
button.setAttribute('type', 'button');
button.addEventListener('click', Twinkle.config.listDialog.display, false);
// use jQuery data on the button to store the current config value
$(button).data({
value: Twinkle.getPref(pref.name),
pref: pref
});
button.appendChild(document.createTextNode('ویرایش آیتمها')); // localized
cell.appendChild(button);
break;
default:
alert('twinkleconfig: unknown data type for preference ' + pref.name);
break;
}
row.appendChild(cell);
// add help tip
cell = document.createElement('td');
cell.style.fontSize = '90%';
cell.style.color = 'gray';
if (pref.helptip) {
// convert mentions of templates in the helptip to clickable links
cell.innerHTML = pref.helptip.replace(/{{(.+?)}}/g,
'{{<a href="' + mw.util.getUrl('Template:') + '$1" target="_blank">$1</a>}}');
}
// add reset link (custom lists don't need this, as their config value isn't displayed on the form)
if (pref.type !== 'customList') {
var resetlink = document.createElement('a');
resetlink.setAttribute('href', '#tw-reset');
resetlink.setAttribute('id', 'twinkle-config-reset-' + pref.name);
resetlink.addEventListener('click', Twinkle.config.resetPrefLink, false);
resetlink.style.cssFloat = 'right';
resetlink.style.margin = '0 0.6em';
resetlink.appendChild(document.createTextNode('بازگشت به مقدار پیشفرض')); // localized
cell.appendChild(resetlink);
}
row.appendChild(cell);
container.appendChild(row);
return true;
});
return true;
});
var footerbox = document.createElement('div');
footerbox.setAttribute('id', 'twinkle-config-buttonpane');
footerbox.style.backgroundColor = '#BCCADF';
footerbox.style.padding = '0.5em';
var button = document.createElement('button');
button.setAttribute('id', 'twinkle-config-submit');
button.setAttribute('type', 'submit');
button.appendChild(document.createTextNode('ذخیره کردن تغییرات')); // localized
footerbox.appendChild(button);
var footerspan = document.createElement('span');
footerspan.className = 'plainlinks';
footerspan.style.marginLeft = '2.4em';
footerspan.style.fontSize = '90%';
var footera = document.createElement('a');
footera.setAttribute('href', '#tw-reset-all');
footera.setAttribute('id', 'twinkle-config-resetall');
footera.addEventListener('click', Twinkle.config.resetAllPrefs, false);
footera.appendChild(document.createTextNode('بازگرداندن مقادیر پیشفرض')); // localized
footerspan.appendChild(footera);
footerbox.appendChild(footerspan);
contentform.appendChild(footerbox);
// since all the section headers exist now, we can try going to the requested anchor
if (location.hash) {
window.location.hash = location.hash;
}
} else if (mw.config.get('wgNamespaceNumber') === mw.config.get('wgNamespaceIds').user &&
mw.config.get('wgTitle').indexOf(mw.config.get('wgUserName')) === 0 &&
mw.config.get('wgPageName').slice(-3) === '.js') {
var box = document.createElement('div');
// Styled in twinkle.css
box.setAttribute('id', 'twinkle-config-headerbox');
var link,
scriptPageName = mw.config.get('wgPageName').slice(mw.config.get('wgPageName').lastIndexOf('/') + 1,
mw.config.get('wgPageName').lastIndexOf('.js'));
if (scriptPageName === 'twinkleoptions') {
// place "why not try the preference panel" notice
box.setAttribute('class', 'config-twopt-box');
if (mw.config.get('wgArticleId') > 0) { // page exists
box.appendChild(document.createTextNode('این صفحه شامل تنظیمات توینکل است. شما میتوانید آن را در ')); // localized
} else { // page does not exist
box.appendChild(document.createTextNode('شما میتوانید تنظیمات سفارشی خود برای توینکل را در ')); // localized
}
link = document.createElement('a');
link.setAttribute('href', mw.util.getUrl(mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').project] + ':توینکل/ترجیحات'));
link.appendChild(document.createTextNode('پنل تنظیمات توینکل')); // localized
box.appendChild(link);
box.appendChild(document.createTextNode('تغییر دهید یا این صفحه را ویرایش کنید.')); // localized
$(box).insertAfter($('#contentSub'));
} else if (['monobook', 'vector', 'cologneblue', 'modern', 'timeless', 'minerva', 'common'].indexOf(scriptPageName) !== -1) {
// place "Looking for Twinkle options?" notice
box.setAttribute('class', 'config-userskin-box');
box.appendChild(document.createTextNode('اگر میخواهید تنظیمات توینکل را تغییر دهید میتوانید از ')); // localized
link = document.createElement('a');
link.setAttribute('href', mw.util.getUrl(mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').project] + ':توینکل/ترجیحات'));
link.appendChild(document.createTextNode('پنل تنظیمات توینکل')); // localized
box.appendChild(link);
box.appendChild(document.createTextNode('استفاده کنید.')); // localized
$(box).insertAfter($('#contentSub'));
}
}
};
// custom list-related stuff
Twinkle.config.listDialog = {};
Twinkle.config.listDialog.addRow = function twinkleconfigListDialogAddRow(dlgtable, value, label) {
var contenttr = document.createElement('tr');
// "remove" button
var contenttd = document.createElement('td');
var removeButton = document.createElement('button');
removeButton.setAttribute('type', 'button');
removeButton.addEventListener('click', function() {
$(contenttr).remove();
}, false);
removeButton.textContent = 'حذف'; // localized
contenttd.appendChild(removeButton);
contenttr.appendChild(contenttd);
// value input box
contenttd = document.createElement('td');
var input = document.createElement('input');
input.setAttribute('type', 'text');
input.className = 'twinkle-config-customlist-value';
input.style.width = '97%';
if (value) {
input.setAttribute('value', value);
}
contenttd.appendChild(input);
contenttr.appendChild(contenttd);
// label input box
contenttd = document.createElement('td');
input = document.createElement('input');
input.setAttribute('type', 'text');
input.className = 'twinkle-config-customlist-label';
input.style.width = '98%';
if (label) {
input.setAttribute('value', label);
}
contenttd.appendChild(input);
contenttr.appendChild(contenttd);
dlgtable.appendChild(contenttr);
};
Twinkle.config.listDialog.display = function twinkleconfigListDialogDisplay(e) {
var $prefbutton = $(e.target);
var curvalue = $prefbutton.data('value');
var curpref = $prefbutton.data('pref');
var dialog = new Morebits.simpleWindow(720, 400);
dialog.setTitle(curpref.label);
dialog.setScriptName('تنظیمات توینکل'); // localized
var dialogcontent = document.createElement('div');
var dlgtable = document.createElement('table');
dlgtable.className = 'wikitable';
dlgtable.style.margin = '1.4em 1em';
dlgtable.style.width = 'auto';
var dlgtbody = document.createElement('tbody');
// header row
var dlgtr = document.createElement('tr');
// top-left cell
var dlgth = document.createElement('th');
dlgth.style.width = '5%';
dlgtr.appendChild(dlgth);
// value column header
dlgth = document.createElement('th');
dlgth.style.width = '35%';
dlgth.textContent = curpref.customListValueTitle ? curpref.customListValueTitle : 'Value';
dlgtr.appendChild(dlgth);
// label column header
dlgth = document.createElement('th');
dlgth.style.width = '60%';
dlgth.textContent = curpref.customListLabelTitle ? curpref.customListLabelTitle : 'Label';
dlgtr.appendChild(dlgth);
dlgtbody.appendChild(dlgtr);
// content rows
var gotRow = false;
$.each(curvalue, function(k, v) {
gotRow = true;
Twinkle.config.listDialog.addRow(dlgtbody, v.value, v.label);
});
// if there are no values present, add a blank row to start the user off
if (!gotRow) {
Twinkle.config.listDialog.addRow(dlgtbody);
}
// final "add" button
var dlgtfoot = document.createElement('tfoot');
dlgtr = document.createElement('tr');
var dlgtd = document.createElement('td');
dlgtd.setAttribute('colspan', '3');
var addButton = document.createElement('button');
addButton.style.minWidth = '8em';
addButton.setAttribute('type', 'button');
addButton.addEventListener('click', function() {
Twinkle.config.listDialog.addRow(dlgtbody);
}, false);
addButton.textContent = 'اضافهکردن'; // localized
dlgtd.appendChild(addButton);
dlgtr.appendChild(dlgtd);
dlgtfoot.appendChild(dlgtr);
dlgtable.appendChild(dlgtbody);
dlgtable.appendChild(dlgtfoot);
dialogcontent.appendChild(dlgtable);
// buttonpane buttons: [Save changes] [Reset] [Cancel]
var button = document.createElement('button');
button.setAttribute('type', 'submit'); // so Morebits.simpleWindow puts the button in the button pane
button.addEventListener('click', function() {
Twinkle.config.listDialog.save($prefbutton, dlgtbody);
dialog.close();
}, false);
button.textContent = 'ذخیره تغییرات'; // localized
dialogcontent.appendChild(button);
button = document.createElement('button');
button.setAttribute('type', 'submit'); // so Morebits.simpleWindow puts the button in the button pane
button.addEventListener('click', function() {
Twinkle.config.listDialog.reset($prefbutton, dlgtbody);
}, false);
button.textContent = 'بازنشانی'; // localized
dialogcontent.appendChild(button);
button = document.createElement('button');
button.setAttribute('type', 'submit'); // so Morebits.simpleWindow puts the button in the button pane
button.addEventListener('click', function() {
dialog.close(); // the event parameter on this function seems to be broken
}, false);
button.textContent = 'لغو'; // localized
dialogcontent.appendChild(button);
dialog.setContent(dialogcontent);
dialog.display();
};
// Resets the data value, re-populates based on the new (default) value, then saves the
// old data value again (less surprising behaviour)
Twinkle.config.listDialog.reset = function twinkleconfigListDialogReset(button, tbody) {
// reset value on button
var $button = $(button);
var curpref = $button.data('pref');
var oldvalue = $button.data('value');
Twinkle.config.resetPref(curpref);
// reset form
var $tbody = $(tbody);
$tbody.find('tr').slice(1).remove(); // all rows except the first (header) row
// add the new values
var curvalue = $button.data('value');
$.each(curvalue, function(k, v) {
Twinkle.config.listDialog.addRow(tbody, v.value, v.label);
});
// save the old value
$button.data('value', oldvalue);
};
Twinkle.config.listDialog.save = function twinkleconfigListDialogSave(button, tbody) {
var result = [];
var current = {};
$(tbody).find('input[type="text"]').each(function(inputkey, input) {
if ($(input).hasClass('twinkle-config-customlist-value')) {
current = { value: input.value };
} else {
current.label = input.value;
// exclude totally empty rows
if (current.value || current.label) {
result.push(current);
}
}
});
$(button).data('value', result);
};
// reset/restore defaults
Twinkle.config.resetPrefLink = function twinkleconfigResetPrefLink(e) {
var wantedpref = e.target.id.substring(21); // "twinkle-config-reset-" prefix is stripped
// search tactics
$(Twinkle.config.sections).each(function(sectionkey, section) {
if (section.hidden || (section.adminOnly && !Morebits.userIsSysop)) {
return true; // continue: skip impossibilities
}
var foundit = false;
$(section.preferences).each(function(prefkey, pref) {
if (pref.name !== wantedpref) {
return true; // continue
}
Twinkle.config.resetPref(pref);
foundit = true;
return false; // break
});
if (foundit) {
return false; // break
}
});
return false; // stop link from scrolling page
};
Twinkle.config.resetPref = function twinkleconfigResetPref(pref) {
switch (pref.type) {
case 'boolean':
document.getElementById(pref.name).checked = Twinkle.defaultConfig[pref.name];
break;
case 'string':
case 'integer':
case 'enum':
document.getElementById(pref.name).value = Twinkle.defaultConfig[pref.name];
break;
case 'set':
$.each(pref.setValues, function(itemkey) {
if (document.getElementById(pref.name + '_' + itemkey)) {
document.getElementById(pref.name + '_' + itemkey).checked = Twinkle.defaultConfig[pref.name].indexOf(itemkey) !== -1;
}
});
break;
case 'customList':
$(document.getElementById(pref.name)).data('value', Twinkle.defaultConfig[pref.name]);
break;
default:
alert('تنظیمات توینکل: نوع داده برای ' + pref.name + ' اشتباه است'); // localized
break;
}
};
Twinkle.config.resetAllPrefs = function twinkleconfigResetAllPrefs() {
// no confirmation message - the user can just refresh/close the page to abort
$(Twinkle.config.sections).each(function(sectionkey, section) {
if (section.hidden || (section.adminOnly && !Morebits.userIsSysop)) {
return true; // continue: skip impossibilities
}
$(section.preferences).each(function(prefkey, pref) {
if (!pref.adminOnly || Morebits.userIsSysop) {
Twinkle.config.resetPref(pref);
}
});
return true;
});
return false; // stop link from scrolling page
};
Twinkle.config.save = function twinkleconfigSave(e) {
Morebits.status.init(document.getElementById('twinkle-config-content'));
var userjs = mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').user] + ':' + mw.config.get('wgUserName') + '/twinkleoptions.js';
var wikipedia_page = new Morebits.wiki.page(userjs, 'ذخیره تنظیمات در ' + userjs); // localized
wikipedia_page.setCallbackParameters(e.target);
wikipedia_page.load(Twinkle.config.writePrefs);
return false;
};
Twinkle.config.writePrefs = function twinkleconfigWritePrefs(pageobj) {
var form = pageobj.getCallbackParameters();
// this is the object which gets serialized into JSON; only
// preferences that this script knows about are kept
var newConfig = {optionsVersion: 2};
// a comparison function is needed later on
// it is just enough for our purposes (i.e. comparing strings, numbers, booleans,
// arrays of strings, and arrays of { value, label })
// and it is not very robust: e.g. compare([2], ["2"]) === true, and
// compare({}, {}) === false, but it's good enough for our purposes here
var compare = function(a, b) {
if (Array.isArray(a)) {
if (a.length !== b.length) {
return false;
}
var asort = a.sort(), bsort = b.sort();
for (var i = 0; asort[i]; ++i) {
// comparison of the two properties of custom lists
if ((typeof asort[i] === 'object') && (asort[i].label !== bsort[i].label ||
asort[i].value !== bsort[i].value)) {
return false;
} else if (asort[i].toString() !== bsort[i].toString()) {
return false;
}
}
return true;
}
return a === b;
};
$(Twinkle.config.sections).each(function(sectionkey, section) {
if (section.adminOnly && !Morebits.userIsSysop) {
return; // i.e. "continue" in this context
}
// reach each of the preferences from the form
$(section.preferences).each(function(prefkey, pref) {
var userValue; // = undefined
// only read form values for those prefs that have them
if (!pref.adminOnly || Morebits.userIsSysop) {
if (!section.hidden) {
switch (pref.type) {
case 'boolean': // read from the checkbox
userValue = form[pref.name].checked;
break;
case 'string': // read from the input box or combo box
case 'enum':
userValue = form[pref.name].value;
break;
case 'integer': // read from the input box
userValue = parseInt(form[pref.name].value, 10);
if (isNaN(userValue)) {
Morebits.status.warn('در حال ذخیره', 'مقداری که برای ' + pref.name + ' (' + pref.value + ') انتخاب کردهاید اشتباه است. ذخیره کردن ادامه مییابد اما مقدار مشکلدار ثبت نمیشود.'); // localized
userValue = null;
}
break;
case 'set': // read from the set of check boxes
userValue = [];
if (pref.setDisplayOrder) {
// read only those keys specified in the display order
$.each(pref.setDisplayOrder, function(itemkey, item) {
if (form[pref.name + '_' + item].checked) {
userValue.push(item);
}
});
} else {
// read all the keys in the list of values
$.each(pref.setValues, function(itemkey) {
if (form[pref.name + '_' + itemkey].checked) {
userValue.push(itemkey);
}
});
}
break;
case 'customList': // read from the jQuery data stored on the button object
userValue = $(form[pref.name]).data('value');
break;
default:
alert('twinkleconfig: unknown data type for preference ' + pref.name);
break;
}
} else if (Twinkle.prefs) {
// Retain the hidden preferences that may have customised by the user from twinkleoptions.js
// undefined if not set
userValue = Twinkle.prefs[pref.name];
}
}
// only save those preferences that are *different* from the default
if (userValue !== undefined && !compare(userValue, Twinkle.defaultConfig[pref.name])) {
newConfig[pref.name] = userValue;
}
});
});
var text =
'// twinkleoptions.js: personal Twinkle preferences file\n' +
'//\n' +
'// NOTE: The easiest way to change your Twinkle preferences is by using the\n' +
'// Twinkle preferences panel, at [[' + Morebits.pageNameNorm + ']].\n' +
'//\n' +
'// This file is AUTOMATICALLY GENERATED. Any changes you make (aside from\n' +
'// changing the configuration parameters in a valid-JavaScript way) will be\n' +
'// overwritten the next time you click "save" in the Twinkle preferences\n' +
'// panel. If modifying this file, make sure to use correct JavaScript.\n' +
'// <no' + 'wiki>\n' +
'\n' +
'window.Twinkle.prefs = ';
text += JSON.stringify(newConfig, null, 2);
text +=
';\n' +
'\n' +
'// </no' + 'wiki>\n' +
'// End of twinkleoptions.js\n';
pageobj.setPageText(text);
pageobj.setEditSummary('ذخیره تنظیمات توینکل: ویرایش خودکار از [[:' + Morebits.pageNameNorm + ']] ([[وو:تل]])'); // localized
pageobj.setCreateOption('recreate');
pageobj.save(Twinkle.config.saveSuccess);
};
Twinkle.config.saveSuccess = function twinkleconfigSaveSuccess(pageobj) {
pageobj.getStatusElement().info('successful');
var noticebox = document.createElement('div');
noticebox.className = 'successbox';
noticebox.style.fontSize = '100%';
noticebox.style.marginTop = '2em';
noticebox.innerHTML = '<p><b>تنظیمات توینکل شما ذخیره شد.</b></p><p>برای دیدن تغییرات باید <b>کاشه مرورگر را خالی کنید</b> (توضیح بیشتر در<a href="' + mw.util.getUrl('WP:BYPASS') + '" title="وو:کاشه">وو:کاشه</a>).</p>'; // localized
Morebits.status.root.appendChild(noticebox);
var noticeclear = document.createElement('br');
noticeclear.style.clear = 'both';
Morebits.status.root.appendChild(noticeclear);
};
})(jQuery);
// </nowiki>
/* ===== twinklewarn.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklewarn.js: Warn module
****************************************
* Mode of invocation: Tab ("Warn")
* Active on: Any page with relevant user name (userspace, contribs,
* etc.), as well as the rollback success page
*/
Twinkle.warn = function twinklewarn() {
if (mw.config.get('wgRelevantUserName')) {
Twinkle.addPortletLink(Twinkle.warn.callback, 'هشدار', 'tw-warn', 'هشدار/اعلان به کاربر'); // localized
if (Twinkle.getPref('autoMenuAfterRollback') &&
mw.config.get('wgNamespaceNumber') === 3 &&
mw.util.getParamValue('vanarticle') &&
!mw.util.getParamValue('friendlywelcome') &&
!mw.util.getParamValue('noautowarn')) {
Twinkle.warn.callback();
}
}
// Modify URL of talk page on rollback success pages, makes use of a
// custom message box in [[MediaWiki:Rollback-success]]
if (mw.config.get('wgAction') === 'rollback') {
var $vandalTalkLink = $('#mw-rollback-success').find('.mw-usertoollinks a').first();
if ($vandalTalkLink.length) {
$vandalTalkLink.css('font-weight', 'bold');
$vandalTalkLink.wrapInner($('<span/>').attr('title', 'در صورت صلاحدید، میتوانید از توینکل برای هشدار به کاربر دربارهٔ ویرایشهایش در این صفحه استفاده کنید.')); // localized
// Can't provide vanarticlerevid as only wgCurRevisionId is provided
var extraParam = 'vanarticle=' + mw.util.rawurlencode(Morebits.pageNameNorm);
var href = $vandalTalkLink.attr('href');
if (href.indexOf('?') === -1) {
$vandalTalkLink.attr('href', href + '?' + extraParam);
} else {
$vandalTalkLink.attr('href', href + '&' + extraParam);
}
}
}
};
// Used to close window when switching to ARV in autolevel
Twinkle.warn.dialog = null;
Twinkle.warn.callback = function twinklewarnCallback() {
if (mw.config.get('wgRelevantUserName') === mw.config.get('wgUserName') &&
!confirm('شما در حال هشدار به خودتان هستید! آیا از انجام این کار مطمئن هستید؟')) { // localized
return;
}
var dialog;
Twinkle.warn.dialog = new Morebits.simpleWindow(600, 440);
dialog = Twinkle.warn.dialog;
dialog.setTitle('هشدار/اعلان به کاربر'); // localized
dialog.setScriptName('توینکل'); // localized
dialog.addFooterLink('انتخاب سطح هشدار', 'en:WP:UWUL#Levels'); // localized
dialog.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#warn'); // localized
var form = new Morebits.quickForm(Twinkle.warn.callback.evaluate);
var main_select = form.append({
type: 'field',
label: 'نوع هشدار/اعلان برای این مشکل را انتخاب کنید', // localized
tooltip: 'ابتدا گروه اصلی هشدار، و سپس یک هشدار اختصاصی برای این مشکل را انتحاب کنید.' // localized
});
var main_group = main_select.append({
type: 'select',
name: 'main_group',
event: Twinkle.warn.callback.change_category
});
var defaultGroup = parseInt(Twinkle.getPref('defaultWarningGroup'), 10);
main_group.append({ type: 'option', label: 'هشدار خرابکاری (نخستین هشدار)', value: 'singlenotice', selected: defaultGroup !== 7 }); // localized
main_group.append({ type: 'option', label: 'آخرین هشدار خرابکاری', value: 'singlewarn', selected: defaultGroup === 7 }); // localized
main_select.append({ type: 'select', name: 'sub_group', event: Twinkle.warn.callback.change_subcategory }); // Will be empty to begin with.
form.append({
type: 'input',
name: 'article',
label: 'صفحهٔ پیوند دادهشده', // localized
value: mw.util.getParamValue('vanarticle') || '',
tooltip: 'میتوان یک صفحه را از درون اعلان پیوند داد، شاید به این دلیل که این اعلان بهواسطهٔ یک خنثیسازی در صفحهٔ مذکور ارسال شدهاست. برای اینکه پیوندی داده نشود، این جعبه را خالی بگذارید.' // localized
});
form.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-warn-revert-messages'
});
var vanrevid = mw.util.getParamValue('vanarticlerevid');
if (vanrevid) {
var message = '';
var query = {};
// If you tried reverting, check if *you* actually reverted
if (!mw.util.getParamValue('noautowarn') && mw.util.getParamValue('vanarticle')) { // Via fluff link
query = {
action: 'query',
titles: mw.util.getParamValue('vanarticle'),
prop: 'revisions',
rvstartid: vanrevid,
rvlimit: 2,
rvdir: 'newer',
rvprop: 'user'
};
new Morebits.wiki.api('بررسی این که واگردانی شما در این صفحه موفق بود یا نه', query, function(apiobj) { // localized
var revertUser = $(apiobj.getResponse()).find('revisions rev')[1].getAttribute('user');
if (revertUser && revertUser !== mw.config.get('wgUserName')) {
message += ' کس دیگری این صفحه را واگردانی کردهاست و ممکن است به کاربر نیز هشدار دادهباشد.'; // localized
$('#twinkle-warn-revert-messages').text('Note:' + message);
}
}).post();
}
// Confirm edit wasn't too old for a warning
query = {
action: 'query',
prop: 'revisions',
rvprop: 'timestamp',
revids: vanrevid
};
new Morebits.wiki.api('گرفتن برچسب زمان نسخهها', query, function(apiobj) { // localized
var vantimestamp = $(apiobj.getResponse()).find('revisions rev').attr('timestamp');
var revDate = new Morebits.date(vantimestamp);
if (vantimestamp && revDate.isValid()) {
if (revDate.add(24, 'hours').isBefore(new Date())) {
message += ' این ویرایش بیشتر از ۲۴ ساعت پیش انجام شدهاست و ممکن است نیازی به هشدار نباشد.'; // localized
$('#twinkle-warn-revert-messages').text('Note:' + message);
}
}
}).post();
}
var more = form.append({ type: 'field', name: 'reasonGroup', label: 'اطلاعات هشدار' }); // localized
more.append({ type: 'textarea', label: 'پیام اختیاری:', name: 'reason', tooltip: 'شاید لازم باشد یک دلیل یا یک اعلان با جزئیات بیشتر اضافه شود' }); // localized
var previewlink = document.createElement('a');
$(previewlink).click(function() {
Twinkle.warn.callbacks.preview(result); // |result| is defined below
});
previewlink.style.cursor = 'pointer';
previewlink.textContent = 'پیشنمایش'; // localized
more.append({ type: 'div', id: 'warningpreview', label: [ previewlink ] });
more.append({ type: 'div', id: 'twinklewarn-previewbox', style: 'display: none' });
more.append({ type: 'submit', label: 'ثبت' }); // localized
var result = form.render();
dialog.setContent(result);
dialog.display();
result.main_group.root = result;
result.previewer = new Morebits.wiki.preview($(result).find('div#twinklewarn-previewbox').last()[0]);
// We must init the first choice (General Note);
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.main_group.dispatchEvent(evt);
};
// This is all the messages that might be dispatched by the code
// Each of the individual templates require the following information:
// label (required): A short description displayed in the dialog
// summary (required): The edit summary used. If an article name is entered, the summary is postfixed with "on [[article]]", and it is always postfixed with ". $summaryAd"
// suppressArticleInSummary (optional): Set to true to suppress showing the article name in the edit summary. Useful if the warning relates to attack pages, or some such.
Twinkle.warn.messages = {
levels: {},
singlenotice: {
'هشدار خرابکاری': {
label: 'هشدار خرابکاری — نخستین هشدار به خرابکار',
summary: 'هشدار خرابکاری'
}
},
singlewarn: {
'آخرین هشدار خرابکاری': {
label: 'آخرین هشدار خرابکاری',
summary: 'آخرین هشدار خرابکاری'
}
}
};
Twinkle.warn.talkpageObj = null;
Twinkle.warn.callback.change_category = function twinklewarnCallbackChangeCategory(e) {
var value = e.target.value;
var sub_group = e.target.root.sub_group;
sub_group.main_group = value;
var old_subvalue = sub_group.value;
var old_subvalue_re;
if (old_subvalue) {
if (value === 'kitchensink') { // Exact match possible in kitchensink menu
old_subvalue_re = new RegExp(mw.util.escapeRegExp(old_subvalue));
} else {
old_subvalue = old_subvalue.replace(/\d*(im)?$/, '');
old_subvalue_re = new RegExp(mw.util.escapeRegExp(old_subvalue) + '(\\d*(?:im)?)$');
}
}
while (sub_group.hasChildNodes()) {
sub_group.removeChild(sub_group.firstChild);
}
var selected = false;
// worker function to create the combo box entries
var createEntries = function(contents, container, wrapInOptgroup, val) {
val = typeof val !== 'undefined' ? val : value; // IE doesn't support default parameters
// level2->2, singlewarn->''; also used to distinguish the
// scaled levels from singlenotice, singlewarn, and custom
var level = val.replace(/^\D+/g, '');
// due to an apparent iOS bug, we have to add an option-group to prevent truncation of text
// (search WT:TW archives for "Problem selecting warnings on an iPhone")
if (wrapInOptgroup && $.client.profile().platform === 'iphone') {
var wrapperOptgroup = new Morebits.quickForm.element({
type: 'optgroup',
label: 'الگوهای موجود' // localized
});
wrapperOptgroup = wrapperOptgroup.render();
container.appendChild(wrapperOptgroup);
container = wrapperOptgroup;
}
$.each(contents, function(itemKey, itemProperties) {
// Skip if the current template doesn't have a version for the current level
if (!!level && !itemProperties[val]) {
return;
}
var key = typeof itemKey === 'string' ? itemKey : itemProperties.value;
var template = key + level;
var elem = new Morebits.quickForm.element({
type: 'option',
label: '{{' + template + '}}: ' + (level ? itemProperties[val].label : itemProperties.label),
value: template
});
// Select item best corresponding to previous selection
if (!selected && old_subvalue && old_subvalue_re.test(template)) {
elem.data.selected = selected = true;
}
var elemRendered = container.appendChild(elem.render());
$(elemRendered).data('messageData', itemProperties);
});
};
switch (value) {
case 'singlenotice':
case 'singlewarn':
createEntries(Twinkle.warn.messages[value], sub_group, true);
break;
case 'singlecombined':
var unSortedSinglets = $.extend({}, Twinkle.warn.messages.singlenotice, Twinkle.warn.messages.singlewarn);
var sortedSingletMessages = {};
Object.keys(unSortedSinglets).sort().forEach(function(key) {
sortedSingletMessages[key] = unSortedSinglets[key];
});
createEntries(sortedSingletMessages, sub_group, true);
break;
case 'custom':
createEntries(Twinkle.getPref('customWarningList'), sub_group, true);
break;
case 'kitchensink':
['level1', 'level2', 'level3', 'level4', 'level4im'].forEach(function(lvl) {
$.each(Twinkle.warn.messages.levels, function(_, levelGroup) {
createEntries(levelGroup, sub_group, true, lvl);
});
});
createEntries(Twinkle.warn.messages.singlenotice, sub_group, true);
createEntries(Twinkle.warn.messages.singlewarn, sub_group, true);
createEntries(Twinkle.getPref('customWarningList'), sub_group, true);
break;
case 'level1':
case 'level2':
case 'level3':
case 'level4':
case 'level4im':
// Creates subgroup regardless of whether there is anything to place in it;
// leaves "Removal of deletion tags" empty for 4im
$.each(Twinkle.warn.messages.levels, function(groupLabel, groupContents) {
var optgroup = new Morebits.quickForm.element({
type: 'optgroup',
label: groupLabel
});
optgroup = optgroup.render();
sub_group.appendChild(optgroup);
// create the options
createEntries(groupContents, optgroup, false);
});
break;
case 'autolevel':
// Check user page to determine appropriate level
var autolevelProc = function() {
var wikitext = Twinkle.warn.talkpageObj.getPageText();
// history not needed for autolevel
var latest = Twinkle.warn.callbacks.dateProcessing(wikitext)[0];
// Pseudo-params with only what's needed to parse the level i.e. no messageData
var params = {
sub_group: old_subvalue,
article: e.target.root.article.value
};
var lvl = 'level' + Twinkle.warn.callbacks.autolevelParseWikitext(wikitext, params, latest)[1];
// Identical to level1, etc. above but explicitly provides the level
$.each(Twinkle.warn.messages.levels, function(groupLabel, groupContents) {
var optgroup = new Morebits.quickForm.element({
type: 'optgroup',
label: groupLabel
});
optgroup = optgroup.render();
sub_group.appendChild(optgroup);
// create the options
createEntries(groupContents, optgroup, false, lvl);
});
// Trigger subcategory change, add select menu, etc.
Twinkle.warn.callback.postCategoryCleanup(e);
};
if (Twinkle.warn.talkpageObj) {
autolevelProc();
} else {
var usertalk_page = new Morebits.wiki.page('بحث کاربر:' + mw.config.get('wgRelevantUserName'), 'بارگیری هشدارهای قبلی'); // localized
usertalk_page.setFollowRedirect(true);
usertalk_page.load(function(pageobj) {
Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj
autolevelProc();
});
}
break;
default:
alert('Unknown warning group in twinklewarn');
break;
}
// Trigger subcategory change, add select menu, etc.
// Here because of the async load for autolevel
if (value !== 'autolevel') {
// reset any autolevel-specific messages while we're here
$('#twinkle-warn-autolevel-message').remove();
Twinkle.warn.callback.postCategoryCleanup(e);
}
};
Twinkle.warn.callback.postCategoryCleanup = function twinklewarnCallbackPostCategoryCleanup(e) {
// clear overridden label on article textbox
Morebits.quickForm.setElementTooltipVisibility(e.target.root.article, true);
Morebits.quickForm.resetElementLabel(e.target.root.article);
// Trigger custom label/change on main category change
Twinkle.warn.callback.change_subcategory(e);
// Use select2 to make the select menu searchable
if (!Twinkle.getPref('oldSelect')) {
$('select[name=sub_group]')
.select2({
width: '100%',
matcher: Morebits.select2.matchers.optgroupFull,
templateResult: Morebits.select2.highlightSearchMatches,
language: {
searching: Morebits.select2.queryInterceptor
}
})
.change(Twinkle.warn.callback.change_subcategory);
$('.select2-selection').keydown(Morebits.select2.autoStart);
mw.util.addCSS(
// prevent dropdown from appearing behind the dialog, just in case
'.select2-container { z-index: 10000; }' +
// Increase height
'.select2-container .select2-dropdown .select2-results > .select2-results__options { max-height: 350px; }' +
// Reduce padding
'.select2-results .select2-results__option { padding-top: 1px; padding-bottom: 1px; }' +
'.select2-results .select2-results__group { padding-top: 1px; padding-bottom: 1px; } ' +
// Adjust font size
'.select2-container .select2-dropdown .select2-results { font-size: 13px; }' +
'.select2-container .selection .select2-selection__rendered { font-size: 13px; }'
);
}
};
Twinkle.warn.callback.change_subcategory = function twinklewarnCallbackChangeSubcategory(e) {
var main_group = e.target.form.main_group.value;
var value = e.target.form.sub_group.value;
// Tags that don't take a linked article, but something else (often a username).
// The value of each tag is the label next to the input field
var notLinkedArticle = {
'uw-agf-sock': 'اختیاری: نام کاربری حساب دیگر (بدون پیشوند کاربر:) ', // localized
'uw-bite': "نام کاربری کاربر «چماقخورده» (بدون پیشوند کاربر:) ", // localized
'uw-socksuspect': 'نام کاربری حساب اصلی، در صورت اطلاع (بدون پیشوند کاربر:) ', // localized
'uw-username': 'نام کاربری ناقض سیاست است زیرا... ', // localized
'uw-aiv': 'اختیاری: نام کاربری گزارششده (بدون پیشوند کاربر:) ' // localized
};
if (['singlenotice', 'singlewarn', 'singlecombined', 'kitchensink'].indexOf(main_group) !== -1) {
if (notLinkedArticle[value]) {
if (Twinkle.warn.prev_article === null) {
Twinkle.warn.prev_article = e.target.form.article.value;
}
e.target.form.article.notArticle = true;
e.target.form.article.value = '';
// change form labels according to the warning selected
Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, false);
Morebits.quickForm.overrideElementLabel(e.target.form.article, notLinkedArticle[value]);
} else if (e.target.form.article.notArticle) {
if (Twinkle.warn.prev_article !== null) {
e.target.form.article.value = Twinkle.warn.prev_article;
Twinkle.warn.prev_article = null;
}
e.target.form.article.notArticle = false;
Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, true);
Morebits.quickForm.resetElementLabel(e.target.form.article);
}
}
// add big red notice, warning users about how to use {{uw-[coi-]username}} appropriately
$('#tw-warn-red-notice').remove();
var $redWarning;
if (value === 'uw-username') {
$redWarning = $("<div style='color: red;' id='tw-warn-red-notice'>{{نام کاربری نامناسب}} <b>نباید</b> برای موارد نقض <b>آشکار</b> سیاستها استفاده شود. " + // localized
"نقضهای آشکار باید مستقیماً در [[وو:تامنام]] گزارش شوند (با استفاده از زبانهٔ بررسی و گزارش پیشرفتهٔ توینکل). " + // localized
'{{نام کاربری نامناسب}} باید تنها در موارد لبمرزی و جهت ورود به گفتگو با کاربر استفاده شود.</div>'); // localized
$redWarning.insertAfter(Morebits.quickForm.getElementLabelObject(e.target.form.reasonGroup));
} else if (value === 'uw-coi-username') {
$redWarning = $("<div style='color: red;' id='tw-warn-red-notice'>{{uw-coi-username}} <b>نباید</b> برای موارد نقض <b>آشکار</b>سیاست نام کاربری استفاده شود. " + // localized
"نقضهای آشکار باید مستقیماً در [[وو:تامنام]] گزارش شوند (با استفاده از زبانهٔ بررسی و گزارش پیشرفتهٔ توینکل). " + // localized
'{{uw-coi-username}} باید تنها در موارد لبمرزی و جهت ورود به گفتگو با کاربر استفاده شود.</div>'); // localized
$redWarning.insertAfter(Morebits.quickForm.getElementLabelObject(e.target.form.reasonGroup));
}
};
Twinkle.warn.callbacks = {
getWarningWikitext: function(templateName, article, reason, isCustom) {
var text = '{{subst:' + templateName;
// add linked article for user warnings
if (article) {
// c&pmove has the source as the first parameter
if (templateName === 'uw-c&pmove') {
text += '|to=' + article;
} else {
text += '|1=' + article;
}
}
if (reason && !isCustom) {
// add extra message
if (templateName === 'uw-csd' || templateName === 'uw-probation' ||
templateName === 'uw-userspacenoindex' || templateName === 'uw-userpage') {
text += "|3=''" + reason + "''";
} else {
text += "|2=''" + reason + "''";
}
}
text += '}}';
if (reason && isCustom) {
// we assume that custom warnings lack a {{{2}}} parameter
text += " ''" + reason + "''";
}
return text + ' ~~~~';
},
showPreview: function(form, templatename) {
// Provided on autolevel, not otherwise
templatename = templatename || form.sub_group.value;
var linkedarticle = form.article.value;
var templatetext;
templatetext = Twinkle.warn.callbacks.getWarningWikitext(templatename, linkedarticle,
form.reason.value, form.main_group.value === 'custom');
form.previewer.beginRender(templatetext, 'User_talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username
},
// Just a pass-through unless the autolevel option was selected
preview: function(form) {
if (form.main_group.value === 'autolevel') {
// Always get a new, updated talkpage for autolevel processing
var usertalk_page = new Morebits.wiki.page('بحث کاربر:' + mw.config.get('wgRelevantUserName'), 'بارگیری هشدارهای قبلی'); // localized
usertalk_page.setFollowRedirect(true);
usertalk_page.load(function(pageobj) {
Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj
var wikitext = pageobj.getPageText();
// history not needed for autolevel
var latest = Twinkle.warn.callbacks.dateProcessing(wikitext)[0];
var params = {
sub_group: form.sub_group.value,
article: form.article.value,
messageData: $(form.sub_group).find('option[value="' + $(form.sub_group).val() + '"]').data('messageData')
};
var template = Twinkle.warn.callbacks.autolevelParseWikitext(wikitext, params, latest)[0];
Twinkle.warn.callbacks.showPreview(form, template);
// If the templates have diverged, fake a change event
// to reload the menu with the updated pageobj
if (form.sub_group.value !== template) {
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
form.main_group.dispatchEvent(evt);
}
});
} else {
Twinkle.warn.callbacks.showPreview(form);
}
},
/**
* Used in the main and autolevel loops to determine when to warn
* about excessively recent, stale, or identical warnings.
* @param {string} wikitext The text of a user's talk page, from getPageText()
* @returns {Object[]} - Array of objects: latest contains most recent
* warning and date; history lists all prior warnings
*/
dateProcessing: function(wikitext) {
var history_re = /<!--\s?Template:([uU]w-.*?)\s?-->.*?(\d{1,2}:\d{1,2}, \d{1,2} \w+ \d{4} \(UTC\))/g;
var history = {};
var latest = { date: new Morebits.date(0), type: '' };
var current;
while ((current = history_re.exec(wikitext)) !== null) {
var template = current[1], current_date = new Morebits.date(current[2]);
if (!(template in history) || history[template].isBefore(current_date)) {
history[template] = current_date;
}
if (!latest.date.isAfter(current_date)) {
latest.date = current_date;
latest.type = template;
}
}
return [latest, history];
},
/**
* Main loop for deciding what the level should increment to. Most of
* this is really just error catching and updating the subsequent data.
* May produce up to two notices in a twinkle-warn-autolevel-messages div
*
* @param {string} wikitext The text of a user's talk page, from getPageText() (required)
* @param {Object} params Params object: sub_group is the template (required);
* article is the user-provided article (form.article) used to link ARV on recent level4 warnings;
* messageData is only necessary if getting the full template, as it's
* used to ensure a valid template of that level exists
* @param {Object} latest First element of the array returned from
* dateProcessing. Provided here rather than processed within to avoid
* repeated call to dateProcessing
* @param {(Date|Morebits.date)} date Date from which staleness is determined
* @param {Morebits.status} statelem Status element, only used for handling error in final execution
*
* @returns {Array} - Array that contains the full template and just the warning level
*/
autolevelParseWikitext: function(wikitext, params, latest, date, statelem) {
var template = params.sub_group.replace(/(.*)\d$/, '$1');
var level; // undefined rather than '' means the isNaN below will return true
if (/\d(?:im)?$/.test(latest.type)) { // level1-4im
level = parseInt(latest.type.replace(/.*(\d)(?:im)?$/, '$1'), 10);
} else if (latest.type) { // Non-numbered warning
// Try to leverage existing categorization of
// warnings, all but one are universally lowercased
var loweredType = /uw-multipleIPs/i.test(template) ? 'uw-multipleIPs' : template.toLowerCase();
// It would be nice to account for blocks, but in most
// cases the hidden message is terminal, not the sig
if (Twinkle.warn.messages.singlewarn[loweredType]) {
level = 3;
} else {
level = 1; // singlenotice or not found
}
}
var $autolevelMessage = $('<div/>', {'id': 'twinkle-warn-autolevel-message'});
if (isNaN(level)) { // No prior warnings found, this is the first
level = 1;
} else if (level > 4 || level < 1) { // Shouldn't happen
var message = 'Unable to parse previous warning level, please manually select a warning level.';
if (statelem) {
statelem.error(message);
} else {
alert(message);
}
return;
} else {
date = date || new Date();
var autoTimeout = new Morebits.date(latest.date.getTime()).add(parseInt(Twinkle.getPref('autolevelStaleDays'), 10), 'days');
if (autoTimeout.isAfter(date)) {
if (level === 4) {
level = 4;
// Basically indicates whether we're in the final Main evaluation or not,
// and thus whether we can continue or need to display the warning and link
if (!statelem) {
var $link = $('<a/>', {
'href': '#',
'text': 'برای باز کردن ابزار بررسی و هشدار پیشرفته اینجا کلیک کنید.', // localized
'css': { 'fontWeight': 'bold' },
'click': function() {
Morebits.wiki.actionCompleted.redirect = null;
Twinkle.warn.dialog.close();
Twinkle.arv.callback(mw.config.get('wgRelevantUserName'));
$('input[name=page]').val(params.article); // Target page
$('input[value=final]').prop('checked', true); // Vandalism after final
}
});
var statusNode = $('<div/>', {
'text': mw.config.get('wgRelevantUserName') + ' اخیراً یک هشدار سطح ۴ دریافت کردهاست (' + latest.type + ') بنابراین احتمالاً گزارش کاربر گزینهٔ بهتری است; ', // localized
'css': {'color': 'red' }
});
statusNode.append($link[0]);
$autolevelMessage.append(statusNode);
}
} else { // Automatically increase severity
level += 1;
}
} else { // Reset warning level if most-recent warning is too old
level = 1;
}
}
// Validate warning level, falling back to the uw-generic series.
// Only a few items are missing a level, and in all but a handful
// of cases, the uw-generic series is explicitly used elsewhere per WP:UTM.
if (params.messageData && !params.messageData['level' + level]) {
template = 'uw-generic';
}
template += level;
$autolevelMessage.prepend($('<div>Will issue a <span style="font-weight: bold;">level ' + level + '</span> template.</div>'));
// After the only other message: the (text-only) staleness note
$('#twinkle-warn-autolevel-message').remove(); // clean slate
$autolevelMessage.insertAfter($('#twinkle-warn-warning-message'));
return [template, level];
},
main: function(pageobj) {
var text = pageobj.getPageText();
var statelem = pageobj.getStatusElement();
var params = pageobj.getCallbackParameters();
var messageData = params.messageData;
// JS somehow didn't get destructured assignment until ES6 so of course IE doesn't support it
var warningHistory = Twinkle.warn.callbacks.dateProcessing(text);
var latest = warningHistory[0];
var history = warningHistory[1];
var now = new Morebits.date(pageobj.getLoadTime());
Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj, just in case
if (params.main_group === 'autolevel') {
// [template, level]
var templateAndLevel = Twinkle.warn.callbacks.autolevelParseWikitext(text, params, latest, now, statelem);
// Only if there's a change from the prior display/load
if (params.sub_group !== templateAndLevel[0] && !confirm('Will issue a {{' + templateAndLevel[0] + '}} template to the user, okay?')) {
statelem.error('aborted per user request');
return;
}
// Update params now that we've selected a warning
params.sub_group = templateAndLevel[0];
messageData = params.messageData['level' + templateAndLevel[1]];
} else if (params.sub_group in history) {
if (new Morebits.date(history[params.sub_group]).add(1, 'day').isAfter(now)) {
if (!confirm('یک ' + params.sub_group + ' همسان در ۲۴ ساعت گذشته صادر شدهاست. \nهمچنان مایل هستید این هشدار/اعلان را بیافزایید؟')) { // localized
statelem.error('به درخواست کاربر لغو شد'); // localized
return;
}
}
}
latest.date.add(1, 'minute'); // after long debate, one minute is max
if (latest.date.isAfter(now)) {
if (!confirm('A ' + latest.type + ' در یک دقیقهٔ گذشته صادر شدهاست. \nهمچنان مایل هستید این هشدار/اعلان را بیافزایید؟')) { // localized
statelem.error('به درخواست کاربر لغو شد'); // localized
return;
}
}
var dateHeaderRegex = now.monthHeaderRegex(), dateHeaderRegexLast, dateHeaderRegexResult;
while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) {
dateHeaderRegexResult = dateHeaderRegexLast;
}
// If dateHeaderRegexResult is null then lastHeaderIndex is never checked. If it is not null but
// \n== is not found, then the date header must be at the very start of the page. lastIndexOf
// returns -1 in this case, so lastHeaderIndex gets set to 0 as desired.
var lastHeaderIndex = text.lastIndexOf('\n==') + 1;
if (text.length > 0) {
text += '\n\n';
}
if (messageData.heading) {
text += '== ' + messageData.heading + ' ==\n';
} else if (!dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex) {
Morebits.status.info('Info', 'یک سرتیتر سطح ۲ برای تاریخ خواهد ساخت، زیرا هیچ سرتیتری برای این ماه یافت نشد'); // localized
text += now.monthHeader() + '\n';
}
text += Twinkle.warn.callbacks.getWarningWikitext(params.sub_group, params.article,
params.reason, params.main_group === 'custom');
if (Twinkle.getPref('showSharedIPNotice') && mw.util.isIPAddress(mw.config.get('wgTitle'))) {
Morebits.status.info('Info', 'افزودن اعلان آیپی مشترک'); // localized
text += '\n{{subst:آگاهی آیپی مشترک}}'; // localized
}
// build the edit summary
var summary;
if (params.main_group === 'custom') {
switch (params.sub_group.substr(-1)) {
case '1':
summary = 'یادآوری کلی'; // localized
break;
case '2':
summary = 'اخطار'; // localized
break;
case '3':
summary = 'هشدار'; // localized
break;
case '4':
summary = 'آخرین هشدار'; // localized
break;
case 'm':
if (params.sub_group.substr(-3) === '4im') {
summary = 'تنها هشدار'; // localized
break;
}
summary = 'اعلان'; // localized
break;
default:
summary = 'اعلان'; // localized
break;
}
summary += ': ' + Morebits.string.toUpperCaseFirstChar(messageData.label);
} else {
// Normalize kitchensink to the 1-4im style
if (params.main_group === 'kitchensink' && !/^D+$/.test(params.sub_group)) {
var sub = params.sub_group.substr(-1);
if (sub === 'm') {
sub = params.sub_group.substr(-3);
}
// Don't overwrite uw-3rr, technically unnecessary
if (/\d/.test(sub)) {
params.main_group = 'level' + sub;
}
}
summary = /^\D+$/.test(params.main_group) ? messageData.summary : messageData[params.main_group].summary;
if (messageData.suppressArticleInSummary !== true && params.article) {
if (params.sub_group === 'uw-agf-sock' ||
params.sub_group === 'uw-socksuspect' ||
params.sub_group === 'uw-aiv') { // these templates require a username
summary += ' of [[:کاربر:' + params.article + ']]'; // localized
} else {
summary += ' on [[:' + params.article + ']]';
}
}
}
summary += '.' + Twinkle.getPref('summaryAd');
pageobj.setPageText(text);
pageobj.setEditSummary(summary);
pageobj.setWatchlist(Twinkle.getPref('watchWarnings'));
pageobj.save();
}
};
Twinkle.warn.callback.evaluate = function twinklewarnCallbackEvaluate(e) {
var userTalkPage = 'بحث_کاربر:' + mw.config.get('wgRelevantUserName');
// First, check to make sure a reason was filled in if uw-username was selected
if (e.target.sub_group.value === 'uw-username' && e.target.article.value.trim() === '') {
alert('لازم است که برای الگوی {{نام کاربری نامناسب}} یک دلیل ارائه کنید.'); // localized
return;
}
// Find the selected <option> element so we can fetch the data structure
var selectedEl = $(e.target.sub_group).find('option[value="' + $(e.target.sub_group).val() + '"]');
// Then, grab all the values provided by the form
var params = {
reason: e.target.reason.value,
main_group: e.target.main_group.value,
sub_group: e.target.sub_group.value,
article: e.target.article.value, // .replace( /^(Image|Category):/i, ':$1:' ), -- apparently no longer needed...
messageData: selectedEl.data('messageData')
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(e.target);
Morebits.wiki.actionCompleted.redirect = userTalkPage;
Morebits.wiki.actionCompleted.notice = 'هشدار دادن کامل شد، بارگیری صفحهٔ بحث تا چند ثانیهٔ دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'User talk page modification');
wikipedia_page.setCallbackParameters(params);
wikipedia_page.setFollowRedirect(true);
wikipedia_page.load(Twinkle.warn.callbacks.main);
};
})(jQuery);
// </nowiki>
/* ===== twinklearv.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklearv.js: ARV module
****************************************
* Mode of invocation: Tab ("ARV")
* Active on: Any page with relevant user name (userspace, contribs, etc.)
*
* Localized for fa.wiktionary (ویکیواژه):
* - The only report venue is the admin noticeboard
* «ویکیواژه:تابلوی اعلانات مدیران», with the subpage
* «ویکیواژه:تابلوی اعلانات مدیران/نامهای کاربری نامناسب» for usernames.
* - SPI (sock/puppet) and AN3 (edit-warring) venues do not exist here and were removed.
*/
// Venue pages on fa.wiktionary
var ADMIN_NOTICEBOARD = 'ویکیواژه:تابلوی اعلانات مدیران';
var USERNAME_NOTICEBOARD = 'ویکیواژه:تابلوی اعلانات مدیران/نامهای کاربری نامناسب';
Twinkle.arv = function twinklearv() {
var username = mw.config.get('wgRelevantUserName');
if (!username || username === mw.config.get('wgUserName')) {
return;
}
var isIP = mw.util.isIPAddress(username);
var title = isIP ? 'گزارش نشانی آیپی به مدیران' : 'گزارش کاربر به مدیران';
Twinkle.addPortletLink(function() {
Twinkle.arv.callback(username, isIP);
}, 'ARV', 'tw-arv', title);
};
Twinkle.arv.callback = function (uid, isIP) {
var Window = new Morebits.simpleWindow(600, 500);
Window.setTitle('گزارش کاربر به مدیران');
Window.setScriptName('Twinkle');
Window.addFooterLink('تابلوی اعلانات مدیران', ADMIN_NOTICEBOARD);
var form = new Morebits.quickForm(Twinkle.arv.callback.evaluate);
var categories = form.append({
type: 'select',
name: 'category',
label: 'نوع گزارش را انتخاب کنید: ',
event: Twinkle.arv.callback.changeCategory
});
categories.append({
type: 'option',
label: 'خرابکاری / گزارش عمومی',
value: 'aiv'
});
categories.append({
type: 'option',
label: 'نام کاربری نامناسب',
value: 'username',
disabled: mw.util.isIPAddress(uid)
});
form.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-arv-blockwarning'
});
form.append({
type: 'field',
label: 'محل کار',
name: 'work_area'
});
form.append({ type: 'submit' });
form.append({
type: 'hidden',
name: 'uid',
value: uid
});
var result = form.render();
Window.setContent(result);
Window.display();
// Check if the user is blocked, update notice
var query = {
action: 'query',
list: 'blocks',
bkprop: 'range|flags',
format: 'json'
};
if (isIP) {
query.bkip = uid;
} else {
query.bkusers = uid;
}
new Morebits.wiki.api('بررسی وضعیت قطع دسترسی کاربر', query, function(apiobj) {
var blocklist = apiobj.getResponse().query.blocks;
if (blocklist.length) {
var block = blocklist[0];
var message = (isIP ? 'این نشانی آیپی' : 'این حساب') + ' هماکنون ' + (block.partial ? 'بهطور جزئی ' : '') + 'قطع دسترسی شده است';
// Start and end differ, range blocked
message += block.rangestart !== block.rangeend ? ' (بهعنوان بخشی از قطع دسترسی محدوده).' : '.';
if (block.partial) {
$('#twinkle-arv-blockwarning').css('color', 'black'); // Less severe
}
$('#twinkle-arv-blockwarning').text(message);
}
}).post();
// We must init the
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.category.dispatchEvent(evt);
};
Twinkle.arv.callback.changeCategory = function (e) {
var value = e.target.value;
var root = e.target.form;
var old_area = Morebits.quickForm.getElements(root, 'work_area')[0];
var work_area = null;
switch (value) {
case 'aiv':
/* falls through */
default:
work_area = new Morebits.quickForm.element({
type: 'field',
label: 'گزارش کاربر برای خرابکاری',
name: 'work_area'
});
work_area.append({
type: 'input',
name: 'page',
label: 'صفحهٔ مرتبط: ',
tooltip: 'برای پیوند ندادن به صفحه در گزارش، خالی بگذارید',
value: mw.util.getParamValue('vanarticle') || '',
event: function(e) {
var value = e.target.value;
var root = e.target.form;
if (value === '') {
root.badid.disabled = root.goodid.disabled = true;
} else {
root.badid.disabled = false;
root.goodid.disabled = root.badid.value === '';
}
}
});
work_area.append({
type: 'input',
name: 'badid',
label: 'شناسهٔ نسخهٔ صفحهٔ هدف هنگام خرابکاری: ',
tooltip: 'برای نبود پیوند تفاوت، خالی بگذارید',
value: mw.util.getParamValue('vanarticlerevid') || '',
disabled: !mw.util.getParamValue('vanarticle'),
event: function(e) {
var value = e.target.value;
var root = e.target.form;
root.goodid.disabled = value === '';
}
});
work_area.append({
type: 'input',
name: 'goodid',
label: 'شناسهٔ آخرین نسخهٔ سالم پیش از خرابکاری صفحهٔ هدف: ',
tooltip: 'برای پیوند تفاوت با نسخهٔ پیشین، خالی بگذارید',
value: mw.util.getParamValue('vanarticlegoodrevid') || '',
disabled: !mw.util.getParamValue('vanarticle') || mw.util.getParamValue('vanarticlerevid')
});
work_area.append({
type: 'checkbox',
name: 'arvtype',
list: [
{
label: 'خرابکاری پس از آخرین اخطار (سطح ۴ یا ۴im)',
value: 'final'
},
{
label: 'خرابکاری در کمتر از یک روز پس از پایان قطع دسترسی',
value: 'postblock'
},
{
label: 'آشکارا یک حساب صرفاً خرابکاری',
value: 'vandalonly',
disabled: mw.util.isIPAddress(root.uid.value)
},
{
label: 'حساب صرفاً برای تبلیغ',
value: 'promoonly',
disabled: mw.util.isIPAddress(root.uid.value)
},
{
label: 'حساب آشکارا یک ربات هرزنامه یا حساب هکشده است',
value: 'spambot'
}
]
});
work_area.append({
type: 'textarea',
name: 'reason',
label: 'توضیح: '
});
work_area = work_area.render();
old_area.parentNode.replaceChild(work_area, old_area);
break;
case 'username':
work_area = new Morebits.quickForm.element({
type: 'field',
label: 'گزارش نام کاربری نامناسب',
name: 'work_area'
});
work_area.append({
type: 'header',
label: 'نوع(های) نام کاربری نامناسب'
});
work_area.append({
type: 'checkbox',
name: 'arvtype',
list: [
{
label: 'نام کاربری گمراهکننده',
value: 'misleading',
tooltip: 'نامهای کاربری گمراهکننده تصور نادرستی دربارهٔ مشارکتکننده ایجاد میکنند؛ برای نمونه تصور اقتدار نابجا یا تصور حساب ربات.'
},
{
label: 'نام کاربری تبلیغاتی',
value: 'promotional',
tooltip: 'نامهای کاربری تبلیغاتی تبلیغ یک شرکت، وبگاه یا گروه هستند.'
},
{
label: 'نام کاربری که استفادهٔ اشتراکی را میرساند',
value: 'shared',
tooltip: 'نامهای کاربری که احتمال استفادهٔ اشتراکی را میرسانند (نام شرکتها یا گروهها) مجاز نیستند.'
},
{
label: 'نام کاربری توهینآمیز',
value: 'offensive',
tooltip: 'نامهای کاربری توهینآمیز ویرایش هماهنگ را دشوار یا ناممکن میکنند.'
},
{
label: 'نام کاربری مخل',
value: 'disruptive',
tooltip: 'نامهای کاربری مخل شامل حملههای شخصی یا نشاندهندهٔ قصد آشکار برای اخلال هستند.'
}
]
});
work_area.append({
type: 'textarea',
name: 'reason',
label: 'توضیح:'
});
work_area = work_area.render();
old_area.parentNode.replaceChild(work_area, old_area);
break;
}
};
Twinkle.arv.callback.evaluate = function(e) {
var form = e.target;
var reason = '';
var comment = '';
if (form.reason) {
comment = form.reason.value;
}
var uid = form.uid.value;
var types;
switch (form.category.value) {
// Report user for vandalism / general report
case 'aiv':
/* falls through */
default:
types = form.getChecked('arvtype');
if (!types.length && comment === '') {
alert('باید دلیلی مشخص کنید');
return;
}
types = types.map(function(v) {
switch (v) {
case 'final':
return 'خرابکاری پس از آخرین اخطار';
case 'postblock':
return 'خرابکاری پس از پایان اخیر قطع دسترسی';
case 'vandalonly':
return 'رفتار آشکارا نشاندهندهٔ حساب صرفاً خرابکاری است';
case 'promoonly':
return 'حساب تنها برای مقاصد تبلیغاتی استفاده میشود';
case 'spambot':
return 'حساب آشکارا یک ربات هرزنامه یا حساب هکشده است';
default:
return 'دلیل نامشخص';
}
}).join('؛ ');
if (form.page.value !== '') {
reason = 'در [[:' + form.page.value + ']]';
if (form.badid.value !== '') {
reason += ' ([[Special:Diff/' + form.goodid.value + '/' + form.badid.value + '|تفاوت]])';
}
reason += ':';
}
if (types) {
reason += ' ' + types;
}
if (comment !== '') {
reason += (reason === '' ? '' : '. ') + comment;
}
reason = reason.trim();
if (!/[.؟!؛]$/.test(reason)) {
reason += '.';
}
reason += ' ~~' + '~~';
reason = reason.replace(/\r?\n/g, '\n*:'); // indent newlines
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = ADMIN_NOTICEBOARD;
Morebits.wiki.actionCompleted.notice = 'گزارش انجام شد';
var aivPage = new Morebits.wiki.page(ADMIN_NOTICEBOARD, 'در حال پردازش گزارش');
aivPage.setFollowRedirect(true);
aivPage.load(function() {
var text = aivPage.getPageText();
// check if user has already been reported
if (text.indexOf('[[Special:Contributions/' + uid + '|') !== -1) {
aivPage.getStatusElement().error('این کاربر پیشتر گزارش شده است؛ گزارش تازهای افزوده نمیشود');
Morebits.status.printUserText(reason, 'متنی که نوشتید در زیر آمده است، در صورت تمایل میتوانید آن را بهصورت دستی زیر گزارش موجود بگذارید:');
return;
}
aivPage.getStatusElement().status('در حال افزودن گزارش تازه...');
aivPage.setEditSummary('گزارش [[Special:Contributions/' + uid + '|' + uid + ']].');
aivPage.setChangeTags(Twinkle.changeTags);
aivPage.setAppendText('\n* [[Special:Contributions/' + uid + '|' + uid + ']] – ' + reason);
aivPage.append();
});
break;
// Report inappropriate username
case 'username':
types = form.getChecked('arvtype').map(function(v) {
switch (v) {
case 'misleading':
return 'گمراهکننده';
case 'promotional':
return 'تبلیغاتی';
case 'shared':
return 'اشتراکی';
case 'offensive':
return 'توهینآمیز';
case 'disruptive':
return 'مخل';
default:
return v;
}
});
reason = '* [[Special:Contributions/' + uid + '|' + uid + ']] – ';
if (types.length) {
reason += 'نقض سیاست نام کاربری؛ نام کاربری ' + types.join('، ') + '. ';
}
if (comment !== '') {
reason += comment + '. ';
}
reason += '~~' + '~~';
reason = reason.replace(/\r?\n/g, '\n*:'); // indent newlines
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = USERNAME_NOTICEBOARD;
Morebits.wiki.actionCompleted.notice = 'گزارش انجام شد';
var uaaPage = new Morebits.wiki.page(USERNAME_NOTICEBOARD, 'در حال پردازش گزارش نام کاربری');
uaaPage.setFollowRedirect(true);
uaaPage.load(function() {
var text = uaaPage.getPageText();
// check if user has already been reported
if (text.indexOf('[[Special:Contributions/' + uid + '|') !== -1) {
uaaPage.getStatusElement().error('این کاربر پیشتر فهرست شده است.');
Morebits.status.printUserText(reason, 'متنی که نوشتید در زیر آمده است، در صورت تمایل میتوانید آن را بهصورت دستی زیر گزارش موجود بگذارید:');
return;
}
uaaPage.getStatusElement().status('در حال افزودن گزارش تازه...');
uaaPage.setEditSummary('گزارش [[Special:Contributions/' + uid + '|' + uid + ']].');
uaaPage.setChangeTags(Twinkle.changeTags);
uaaPage.setPageText(text + '\n' + reason);
uaaPage.save();
});
break;
}
};
Twinkle.addInitCallback(Twinkle.arv, 'arv');
})(jQuery);
// </nowiki>
/* ===== twinkleblock.js ===== */
// <nowiki>
(function($) {
var api = new mw.Api(), relevantUserName;
var menuFormattedNamespaces = $.extend({}, mw.config.get('wgFormattedNamespaces'));
menuFormattedNamespaces[0] = '(مقاله)'; // localized
/*
****************************************
*** twinkleblock.js: Block module
****************************************
* Mode of invocation: Tab ("Block")
* Active on: Any page with relevant user name (userspace, contribs, etc.)
*/
Twinkle.block = function twinkleblock() {
// should show on Contributions or Block pages, anywhere there's a relevant user
if (Morebits.userIsSysop && mw.config.get('wgRelevantUserName')) {
Twinkle.addPortletLink(Twinkle.block.callback, 'بستن', 'tw-block', 'قطع دسترسی یک کاربر'); // localized
}
};
Twinkle.block.callback = function twinkleblockCallback() {
if (mw.config.get('wgRelevantUserName') === mw.config.get('wgUserName') &&
!confirm('شما در حال قطع دسترسی خودتان هستید! آیا مطمئنید که میخواهید ادامه دهید؟')) { // localized
return;
}
Twinkle.block.currentBlockInfo = undefined;
Twinkle.block.field_block_options = {};
Twinkle.block.field_template_options = {};
var Window = new Morebits.simpleWindow(650, 530);
// need to be verbose about who we're blocking
Window.setTitle('قطع دسترسی یا افزودن الگوی قطع دسترسی برای ' + mw.config.get('wgRelevantUserName')); // localized
Window.setScriptName('توینکل'); // localized
// Window.addFooterLink('Block templates', 'Template:Uw-block/doc/Block_templates'); // localized [removed]
Window.addFooterLink('سیاست قطع دسترسی', 'وو:بستن'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#block'); // localized
var form = new Morebits.quickForm(Twinkle.block.callback.evaluate);
var actionfield = form.append({
type: 'field',
label: 'نوع عمل' // localized
});
actionfield.append({
type: 'checkbox',
name: 'actiontype',
event: Twinkle.block.callback.change_action,
list: [
{
label: 'قطع دسترسی کاربر', // localized
value: 'قطع دسترسی', // localized
tooltip: 'با استفاده از گزینههای ارائه شده، دسترسی کاربر را قطع کنید. اگر قطع دسترسی موردی انتخاب نشود، قطع دسترسی کلی خواهد بود.', // localized
checked: true
},
{
label: 'قطع دسترسی موردی', // localized
value: 'partial',
tooltip: 'انجام یا ثبت قطع دسترسی موردی', // localized
checked: Twinkle.getPref('defaultToPartialBlocks')
},
{
label: 'افزودن الگوی قطع دسترسی به صفحهٔ بحث کاربر', // localized
value: 'template',
tooltip: 'اگر مدیری که قطع دسترسی را انجام داده فراموش کرده که الگوی قطع دسترسی را در صفحهٔ بحث کاربر بگذارد، یا شما حسابی را قطع دسترسی کردید بدون آن که الگوی مذکور را در صفحهٔ بحث کاربر بگذارید، میتوانید از این روش برای ثبت الگوی مناسب استفاده کنید.', // localized
checked: true
}
]
});
form.append({ type: 'field', label: 'تنظیم اولیه', name: 'field_preset' }); // localized
form.append({ type: 'field', label: 'گزینههای الگو', name: 'field_template_options' }); // localized
form.append({ type: 'field', label: 'گزینههای بستن', name: 'field_block_options' }); // localized
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
result.root = result;
Twinkle.block.fetchUserInfo(function() {
// clean up preset data (defaults, etc.), done exactly once, must be before Twinkle.block.callback.change_action is called
Twinkle.block.transformBlockPresets();
if (Twinkle.block.currentBlockInfo) {
Window.addFooterLink('باز کردن کاربر', 'Special:Unblock/' + mw.config.get('wgRelevantUserName'), true); // localized
}
// init the controls after user and block info have been fetched
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.actiontype[0].dispatchEvent(evt);
});
};
Twinkle.block.fetchUserInfo = function twinkleblockFetchUserInfo(fn) {
api.get({
format: 'json',
action: 'query',
list: 'blocks|users|logevents',
letype: 'block',
lelimit: 1,
bkusers: mw.config.get('wgRelevantUserName'),
ususers: mw.config.get('wgRelevantUserName'),
letitle: 'User:' + mw.config.get('wgRelevantUserName')
})
.then(function(data) {
var blockinfo = data.query.blocks[0],
userinfo = data.query.users[0];
Twinkle.block.isRegistered = !!userinfo.userid;
relevantUserName = Twinkle.block.isRegistered ? 'User:' + mw.config.get('wgRelevantUserName') : mw.config.get('wgRelevantUserName');
if (blockinfo) {
// handle frustrating system of inverted boolean values
blockinfo.disabletalk = blockinfo.allowusertalk === undefined;
blockinfo.hardblock = blockinfo.anononly === undefined;
Twinkle.block.currentBlockInfo = blockinfo;
}
Twinkle.block.hasBlockLog = !!data.query.logevents.length;
// Used later to check if block status changed while filling out the form
Twinkle.block.blockLogId = Twinkle.block.hasBlockLog ? data.query.logevents[0].logid : false;
if (typeof fn === 'function') {
return fn();
}
}, function(msg) {
Morebits.status.init($('div[name="currentblock"] span').last()[0]);
Morebits.status.warn('خطا در دریافت اطلاعات کاربر', msg); // localized
});
};
Twinkle.block.callback.saveFieldset = function twinkleblockCallbacksaveFieldset(fieldset) {
Twinkle.block[$(fieldset).prop('name')] = {};
$(fieldset).serializeArray().forEach(function(el) {
// namespaces and pages for partial blocks are overwritten
// here, but we're handling them elsewhere so that's fine
Twinkle.block[$(fieldset).prop('name')][el.name] = el.value;
});
};
Twinkle.block.callback.change_action = function twinkleblockCallbackChangeAction(e) {
var field_preset, field_template_options, field_block_options, $form = $(e.target.form);
// Make ifs shorter
var blockBox = $form.find('[name=actiontype][value=block]').is(':checked');
var templateBox = $form.find('[name=actiontype][value=template]').is(':checked');
var partial = $form.find('[name=actiontype][value=partial]');
var partialBox = partial.is(':checked');
var blockGroup = partialBox ? Twinkle.block.blockGroupsPartial : Twinkle.block.blockGroups;
partial.prop('disabled', !blockBox && !templateBox);
Twinkle.block.callback.saveFieldset($('[name=field_block_options]'));
Twinkle.block.callback.saveFieldset($('[name=field_template_options]'));
if (blockBox) {
field_preset = new Morebits.quickForm.element({ type: 'field', label: 'Preset', name: 'field_preset' });
field_preset.append({
type: 'select',
name: 'preset',
label: 'انتخاب یک تنظیم اولیه:', // localized
event: Twinkle.block.callback.change_preset,
list: Twinkle.block.callback.filtered_block_groups(blockGroup)
});
field_block_options = new Morebits.quickForm.element({ type: 'field', label: 'گزینههای قطع دسترسی', name: 'field_block_options' }); // localized
field_block_options.append({ type: 'div', name: 'hasblocklog', label: ' ' });
field_block_options.append({ type: 'div', name: 'currentblock', label: ' ' });
field_block_options.append({
type: 'select',
name: 'expiry_preset',
label: 'زمان سرآمدن:', // localized
event: Twinkle.block.callback.change_expiry,
list: [
{ label: 'سفارشی', value: 'custom', selected: true }, // localized
{ label: 'بیپایان', value: 'infinity' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۲۴ ساعت', value: '24 hours' }, // localized
{ label: '۳۱ ساعت', value: '31 hours' }, // localized
{ label: '۳۶ ساعت', value: '36 hours' }, // localized
{ label: '۴۸ ساعت', value: '48 hours' }, // localized
{ label: '۶۰ ساعت', value: '60 hours' }, // localized
{ label: '۷۲ ساعت', value: '72 hours' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۶ ماه', value: '6 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: '۲ سال', value: '2 years' }, // localized
{ label: '۳ سال', value: '3 years' } // localized
]
});
field_block_options.append({
type: 'input',
name: 'expiry',
label: 'زمان سرآمدن سفارشی', // localized
tooltip: 'میتوانید یک زمان نسبی به انگلیسی وارد کنید (مثلاً 1 minute یا 19 days) یا این که یک زمان مشخص را به صورت yyyymmddhhmm با ارقام انگلسی وارد کنید (مثلاً 200602011405 میشود ساعت ۱۴:۰۵ روز ۱ فوریهٔ ۲۰۰۶)', // localizde
value: Twinkle.block.field_block_options.expiry || Twinkle.block.field_template_options.template_expiry
});
if (partialBox) { // Partial block
field_block_options.append({
type: 'select',
multiple: true,
name: 'pagerestrictions',
label: 'صفحههای مورد قطع دسترسی', // localized
value: '',
tooltip: 'حداکثر ۱۰ مورد.' // localized
});
var ns = field_block_options.append({
type: 'select',
multiple: true,
name: 'namespacerestrictions',
label: 'فضاهای نام مورد قطع دسترسی', // localized
value: '',
tooltip: 'جلوگیری از ویرایش این فضاهای نام.', // localized
});
$.each(menuFormattedNamespaces, function(number, name) {
// Ignore -1: Special; -2: Media; and 2300-2303: Gadget (talk) and Gadget definition (talk)
if (number >= 0 && number < 830) {
ns.append({ type: 'option', label: name, value: number });
}
});
}
var blockoptions = [
{
checked: Twinkle.block.field_block_options.nocreate,
label: 'جلوگیری از ایجاد حساب', // localized
name: 'nocreate',
value: '1'
},
{
checked: Twinkle.block.field_block_options.noemail,
label: 'جلوگیری از ارسال ایمیل', // localized
name: 'noemail',
value: '1'
},
{
checked: Twinkle.block.field_block_options.disabletalk,
label: 'جلوگیری از ویرایش صفحهٔ بحث در مدت قطع دسترسی', // localized
name: 'disabletalk',
value: '1',
tooltip: partialBox ? 'در صورت اعمال قطع دسترسی موردی، این گزینه باید خاموش باشد مگر اینکه شما بخواهید از ویرایش کاربر در فضای نام بحث کاربر نیز جلوگیری کنید' : '' // localized
}
];
if (Twinkle.block.isRegistered) {
blockoptions.push({
checked: Twinkle.block.field_block_options.autoblock,
label: 'بستن خودکار نشانیهایآیپی', // localized
name: 'autoblock',
value: '1'
});
} else {
blockoptions.push({
checked: Twinkle.block.field_block_options.hardblock,
label: 'بستن حسابهای ثبتنام کرده از همین نشانی آیپی', // localized
name: 'hardblock',
value: '1'
});
}
blockoptions.push({
checked: Twinkle.block.field_block_options.watchuser,
label: 'بستن صفحههای کاربر و بحث کاربر', // localized
name: 'watchuser',
value: '1'
});
field_block_options.append({
type: 'checkbox',
name: 'blockoptions',
list: blockoptions
});
field_block_options.append({
type: 'textarea',
label: 'دلیل (برای سیاههٔ قطع دسترسی):', // localized
name: 'reason',
tooltip: 'لطفاً جزئیات مفیدی را به پیام پیشفرض اضافه کنید.', // localized
value: Twinkle.block.field_block_options.reason
});
/*
field_block_options.append({
type: 'div',
name: 'filerlog_label',
label: 'See also:',
style: 'display:inline-block;font-style:normal !important',
tooltip: 'Insert a "see also" message to indicate whether the filter log or deleted contributions played a role in the decision to block.'
});
field_block_options.append({
type: 'checkbox',
name: 'filter_see_also',
event: Twinkle.block.callback.toggle_see_alsos,
style: 'display:inline-block; margin-right:5px',
list: [
{
label: 'Filter log',
checked: false,
value: 'filter log'
}
]
});
field_block_options.append({
type: 'checkbox',
name: 'deleted_see_also',
event: Twinkle.block.callback.toggle_see_alsos,
style: 'display:inline-block',
list: [
{
label: 'Deleted contribs',
checked: false,
value: 'deleted contribs'
}
]
});
*/ // localized [removed]
if (Twinkle.block.currentBlockInfo) {
field_block_options.append({ type: 'hidden', name: 'reblock', value: '1' });
}
}
if (templateBox) {
field_template_options = new Morebits.quickForm.element({ type: 'field', label: 'گزینههای الگو', name: 'field_template_options' }); // localized
field_template_options.append({
type: 'select',
name: 'template',
label: 'Choose talk page template:',
event: Twinkle.block.callback.change_template,
list: Twinkle.block.callback.filtered_block_groups(blockGroup, true),
value: Twinkle.block.field_template_options.template
});
field_template_options.append({
type: 'input',
name: 'article',
display: 'none',
label: 'Linked page',
value: '',
tooltip: 'A page can be linked within the notice, perhaps if it was the primary target of disruption. Leave empty for no page to be linked.'
});
// Only visible if partial and not blocking
field_template_options.append({
type: 'input',
name: 'area',
display: 'none',
label: 'Area blocked from',
value: '',
tooltip: 'توضیح اختیاری صفحات یا مکانهای نام کاربر از ویرایش مسدود شده است.' // localized
});
if (!blockBox) {
field_template_options.append({
type: 'input',
name: 'template_expiry',
display: 'none',
label: 'Period of blocking: ',
value: '',
tooltip: 'مدت زمان مسدود شدن برای نمونه ، ۲۴ ساعت ، ۲ هفته ، نامشخص و غیره ...' // localized
});
}
field_template_options.append({
type: 'input',
name: 'block_reason',
label: '"You have been blocked for ..." ',
display: 'none',
tooltip: 'یک دلیل اختیاری برای جایگزینی دلیل عمومی پیش فرض. فقط برای الگوهای بستن عمومی موجود است.', // localized
value: Twinkle.block.field_template_options.block_reason
});
if (blockBox) {
field_template_options.append({
type: 'checkbox',
name: 'blank_duration',
list: [
{
label: 'قرار ندادن تاریخ انقضا در الگو', // localized
checked: Twinkle.block.field_template_options.blank_duration,
tooltip: 'به جای اینکه مدت زمان را اضافه کنید، الگوی بستن را بخوانید "شما به طور موقت مسدود شدهاید ..."' // localized
}
]
});
} else {
field_template_options.append({
type: 'checkbox',
list: [
{
label: 'جلوگیری از ویرایش صفحهٔ بحث', // localized
name: 'notalk',
checked: Twinkle.block.field_template_options.notalk,
tooltip: 'Make the block template state that the user\'s talk page access has been removed'
},
{
label: 'جلوگیری از ارسال ایمیل', // localized
name: 'noemail_template',
checked: Twinkle.block.field_template_options.noemail_template,
tooltip: 'If the area is not provided, make the block template state that the user\'s email access has been removed'
},
{
label: 'جلوگیری از ایجاد حساب کاربری', // localized
name: 'nocreate_template',
checked: Twinkle.block.field_template_options.nocreate_template,
tooltip: 'If the area is not provided, make the block template state that the user\'s ability to create accounts has been removed'
}
]
});
}
var $previewlink = $('<a id="twinkleblock-preivew-link">Preview</a>');
$previewlink.off('click').on('click', function() {
Twinkle.block.callback.preview($form[0]);
});
$previewlink.css({cursor: 'pointer'});
field_template_options.append({ type: 'div', id: 'blockpreview', label: [ $previewlink[0] ] });
field_template_options.append({ type: 'div', id: 'twinkleblock-previewbox', style: 'display: none' });
}
var oldfield;
if (field_preset) {
oldfield = $form.find('fieldset[name="field_preset"]')[0];
oldfield.parentNode.replaceChild(field_preset.render(), oldfield);
} else {
$form.find('fieldset[name="field_preset"]').hide();
}
if (field_block_options) {
oldfield = $form.find('fieldset[name="field_block_options"]')[0];
oldfield.parentNode.replaceChild(field_block_options.render(), oldfield);
$form.find('[name=pagerestrictions]').select2({
width: '100%',
placeholder: 'صفحات را برای بستن کاربر انتخاب کنید', // localized
language: {
errorLoading: function() {
return 'Incomplete or invalid search term';
}
},
maximumSelectionLength: 10, // Software limitation [[phab:T202776]]
minimumInputLength: 1, // prevent ajax call when empty
ajax: {
url: mw.util.wikiScript('api'),
dataType: 'json',
delay: 100,
data: function(params) {
var title = mw.Title.newFromText(params.term);
if (!title) {
return;
}
return {
'action': 'query',
'format': 'json',
'list': 'allpages',
'apfrom': title.title,
'apnamespace': title.namespace,
'aplimit': '10'
};
},
processResults: function(data) {
return {
results: data.query.allpages.map(function(page) {
var title = mw.Title.newFromText(page.title, page.ns).toText();
return {
id: title,
text: title
};
})
};
}
},
templateSelection: function(choice) {
return $('<a>').text(choice.text).attr({
href: mw.util.getUrl(choice.text),
target: '_blank'
});
}
});
$form.find('[name=namespacerestrictions]').select2({
width: '100%',
matcher: Morebits.select2.matchers.wordBeginning,
language: {
searching: Morebits.select2.queryInterceptor
},
templateResult: Morebits.select2.highlightSearchMatches,
placeholder: 'Select namespaces to block user from'
});
mw.util.addCSS(
// prevent dropdown from appearing behind the dialog, just in case
'.select2-container { z-index: 10000; }' +
// Reduce padding
'.select2-results .select2-results__option { padding-top: 1px; padding-bottom: 1px; }' +
// Adjust font size
'.select2-container .select2-dropdown .select2-results { font-size: 13px; }' +
'.select2-container .selection .select2-selection__rendered { font-size: 13px; }' +
// Remove black border
'.select2-container--default.select2-container--focus .select2-selection--multiple { border: 1px solid #aaa; }' +
// Make the tiny cross larger
'.select2-selection__choice__remove { font-size: 130%; }'
);
} else {
$form.find('fieldset[name="field_block_options"]').hide();
// Clear select2 options
$form.find('[name=pagerestrictions]').val(null).trigger('change');
$form.find('[name=namespacerestrictions]').val(null).trigger('change');
}
if (field_template_options) {
oldfield = $form.find('fieldset[name="field_template_options"]')[0];
oldfield.parentNode.replaceChild(field_template_options.render(), oldfield);
e.target.form.root.previewer = new Morebits.wiki.preview($(e.target.form.root).find('#twinkleblock-previewbox').last()[0]);
} else {
$form.find('fieldset[name="field_template_options"]').hide();
}
if (Twinkle.block.hasBlockLog) {
var $blockloglink = $('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: mw.config.get('wgRelevantUserName'), type: 'block'}) + '">block log</a>)');
Morebits.status.init($('div[name="hasblocklog"] span').last()[0]);
Morebits.status.warn('This user has been blocked in the past', $blockloglink[0]);
}
if (Twinkle.block.currentBlockInfo) {
Morebits.status.init($('div[name="currentblock"] span').last()[0]);
// list=blocks without bkprops (as we do in fetchUerInfo)
// returns partial: '' if the user is partially blocked
var statusStr = relevantUserName + ' is ' + (Twinkle.block.currentBlockInfo.partial === '' ? 'partially blocked' : 'blocked sitewide');
if (Twinkle.block.currentBlockInfo.expiry === 'infinity') {
statusStr += ' (indef)';
} else if (new Morebits.date(Twinkle.block.currentBlockInfo.expiry).isValid()) {
statusStr += ' (expires ' + new Morebits.date(Twinkle.block.currentBlockInfo.expiry).calendar('utc') + ')';
}
var infoStr = 'برای تغییر مسدود شدن سوال کنید'; // localized
if (Twinkle.block.currentBlockInfo.partial === undefined && partialBox) {
infoStr += ', converting to a partial block';
} else if (Twinkle.block.currentBlockInfo.partial === '' && !partialBox) {
infoStr += ', converting to a sitewide block';
}
Morebits.status.warn(statusStr, infoStr);
Twinkle.block.callback.update_form(e, Twinkle.block.currentBlockInfo);
}
if (templateBox) {
// make sure all the fields are correct based on defaults
if (blockBox) {
Twinkle.block.callback.change_preset(e);
} else {
Twinkle.block.callback.change_template(e);
}
}
};
/*
* Keep alphabetized by key name, Twinkle.block.blockGroups establishes
* the order they will appear in the interface
*
* Block preset format, all keys accept only 'true' (omit for false) except where noted:
* <title of block template> : {
* autoblock: <autoblock any IP addresses used (for registered users only)>
* disabletalk: <disable user from editing their own talk page while blocked>
* expiry: <string - expiry timestamp, can include relative times like "5 months", "2 weeks" etc>
* forAnonOnly: <show block option in the interface only if the relevant user is an IP>
* forRegisteredOnly: <show block option in the interface only if the relevant user is registered>
* label: <string - label for the option of the dropdown in the interface (keep brief)>
* noemail: prevent the user from sending email through Special:Emailuser
* pageParam: <set if the associated block template accepts a page parameter>
* prependReason: <string - prepends the value of 'reason' to the end of the existing reason, namely for when revoking talk page access>
* nocreate: <block account creation from the user's IP (for anonymous users only)>
* nonstandard: <template does not conform to stewardship of WikiProject User Warnings and may not accept standard parameters>
* reason: <string - block rationale, as would appear in the block log,
* and the edit summary for when adding block template, unless 'summary' is set>
* reasonParam: <set if the associated block template accepts a reason parameter>
* sig: <string - set to ~~~~ if block template does not accept "true" as the value, or set null to omit sig param altogether>
* summary: <string - edit summary for when adding block template to user's talk page, if not set, 'reason' is used>
* suppressArticleInSummary: <set to suppress showing the article name in the edit summary, as with attack pages>
* templateName: <string - name of template to use (instead of key name), entry will be omitted from the Templates list.
* (e.g. use another template but with different block options)>
* useInitialOptions: <when preset is chosen, only change given block options, leave others as they were>
*
* WARNING: 'anononly' and 'allowusertalk' are enabled by default.
* To disable, set 'hardblock' and 'disabletalk', respectively
*/
Twinkle.block.blockPresetsInfo = {
'anonblock': {
expiry: '31 hours',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{anonblock}}',
sig: '~~~~'
},
'anonblock - school': {
expiry: '36 hours',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{anonblock}} <!-- Likely a school based on behavioral evidence -->',
templateName: 'anonblock',
sig: '~~~~'
},
'blocked proxy': {
expiry: '1 year',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
hardblock: true,
reason: '{{پروکسی باز}}', // localized
sig: null
},
'CheckUser block': {
expiry: '1 week',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{قطع دسترسی بازرسی کاربر}}', // localized
sig: '~~~~'
},
'checkuserblock-account': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{بندایش بازرسی کاربر-حساب کاربری}}', // localized
sig: '~~~~'
},
'checkuserblock-wide': {
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{checkuserblock-wide}}',
sig: '~~~~'
},
'colocationwebhost': {
expiry: '1 year',
forAnonOnly: true,
nonstandard: true,
reason: '{{میزبانیوبسرور}}', // localized
sig: null
},
'oversightblock': {
autoblock: true,
expiry: 'infinity',
nocreate: true,
nonstandard: true,
reason: '{{OversightBlock}}',
sig: '~~~~'
},
'school block': {
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{school block}}',
sig: '~~~~'
},
'spamblacklistblock': {
forAnonOnly: true,
expiry: '1 month',
disabletalk: true,
nocreate: true,
reason: '{{spamblacklistblock}} <!-- editor only attempts to add blacklisted links, see [[Special:Log/spamblacklist]] -->'
},
// Placeholder for when we add support for rangeblocks
// 'rangeblock' : {
// reason: '{{rangeblock}}',
// nocreate: true,
// nonstandard: true,
// forAnonOnly: true,
// sig: '~~~~'
// },
'tor': {
expiry: '1 year',
forAnonOnly: true,
nonstandard: true,
reason: '{{Tor}}',
sig: null
},
'webhostblock': {
expiry: '1 year',
forAnonOnly: true,
nonstandard: true,
reason: '{{میزبان وب بستهشده}}', // localized
sig: null
},
// uw-prefixed
'uw-3block': {
autoblock: true,
expiry: '24 hours',
nocreate: true,
pageParam: true,
reason: 'نقض[[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]]', // localized
summary: 'برای نقض [[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]] شما از ویرایش منع شدهاید' // localized
},
'uw-ablock': {
autoblock: true,
expiry: '31 hours',
forAnonOnly: true,
nocreate: true,
pageParam: true,
reasonParam: true,
summary: 'آدرس IP شما از ویرایش منع شده است', // localized
suppressArticleInSummary: true
},
'uw-adblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: 'استفاده از ویکیواژه به عنوان [[ویکیواژه:هرزنامه|هرزنامه]] یا [[ویکیواژه:ویکیواژه چه چیزی نیست|تبلیغات]] ', // localized
summary: 'شما به دلیل [[ویکیواژه:ویکیواژه چه چیزی نیست|ویکیواژه چه چیزی نیست]] از ویرایش منع شدهاید' // localized
},
'uw-aeblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: '[[WP:Arbitration enforcement|Arbitration enforcement]]',
reasonParam: true,
summary: 'شما به دلیل نقض [[WP:Arbitration|تصمیم داوری]] از ویرایش منع شده اید' // localized
},
'uw-bioblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: 'نقض سیایت شرح حال [[ویکیواژه:زندگینامه زندگان|زندگینامه زندگان]] ', // localized
summary: 'You have been blocked from editing for violations of Wikipedia\'s [[ویکیواژه:زندگینامه زندگان]]'
},
'uw-block': {
autoblock: true,
expiry: '24 hours',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reasonParam: true,
summary: 'شما از ویرایش منع شدهاید', // localized
suppressArticleInSummary: true
},
'uw-blockindef': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reasonParam: true,
summary: 'شما به طور نامحدود از ویرایش منع شدهاید', // localized
suppressArticleInSummary: true
},
'uw-blocknotalk': {
disabletalk: true,
pageParam: true,
reasonParam: true,
summary: 'شما از ویرایش منع شدهاید و دسترسی به صفحه بحث کاربر غیرفعال شده است', // localized
suppressArticleInSummary: true
},
'uw-botblock': {
forRegisteredOnly: true,
pageParam: true,
reason: 'اجرای اسکریپت [[ویکیواژه:ربات|ربات]] بدون [[ویکیواژه:سیاست رباترانی/درخواست مجوز|مجوز]]', // localized
summary: 'You have been blocked from editing because it appears you are running a [[ویکیواژه:ربات]] without [[ویکیواژه:سیاست رباترانی/درخواست مجوز]]' // localized
},
'uw-botublock': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-botublock}} <!-- Username implies a bot, soft block -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما نشان می دهد که این یک حساب [[ویکیواژه:ربات|ربات]] است ، که در حال حاضر تأیید نشده است.' // localized
},
'uw-causeblock': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-causeblock}} <!-- Username represents a non-profit, soft block -->',
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما این تصور را ایجاد میکند که این حساب نماینده یک گروه، سازمان یا وب سایت است' // localized
},
'uw-compblock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: 'امنیت حساب کاربری', // localized
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا اعتقاد بر این است که [[ویکیواژه:امنیت حساب کاربری|امنیت حساب شما]] به خطر افتادهاست.' // localized
},
'uw-copyrightblock': {
autoblock: true,
expiry: '24 hours',
nocreate: true,
pageParam: true,
reason: '[[WP:Copyright violations|Copyright violations]]',
summary: 'برای ادامه [[WP:COPYVIO|نقض حق نسخه برداری]] از ویرایش منع شدهاید' // localized
},
'uw-dblock': {
autoblock: true,
nocreate: true,
reason: 'حذف مدام محتوا', // localized
pageParam: true,
summary: 'به دلیل [[ویکیواژه:خرابکاری|خرابکاری]] از ویرایش منع شدهاید' // localized
},
'uw-disruptblock': {
autoblock: true,
nocreate: true,
reason: '[[ویکیواژه:ویرایش اخلالگرانه|ویرایش اخلالگرانه]]', // localized
summary: 'به دلیل [[ویکیواژه:ویرایش اخلالگرانه|ویرایش اخلالگرانه]] از ویرایش منع شدهاید' // localized
},
'uw-efblock': {
autoblock: true,
nocreate: true,
reason: 'Deliberately triggering the [[ویکیواژه:پالایه ویرایش]]', // localized
summary: 'شما به دلیل ویرایشهای مخرب که مرتباً باعث [[ویکیواژه:پالایه ویرایش|پالایه ویرایش]] شدهاند، از ویرایش منع شدهاید.' // localized
},
'uw-ewblock': {
autoblock: true,
expiry: '24 hours',
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]]', // localized
summary: 'برای جلوگیری از [[ویکیواژه:ویرایش اخلالگرانه|اختلال]] بیشتر ناشی از درگیری شما در [[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]]، از ویرایش منع شدهاید.' // localized
},
'uw-hblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:حمله شخصی ممنوع|حمله شخصی ممنوع]] یا [[ویکیواژه:آزار و اذیت|آزار و اذیت]]', // localized
summary: 'شما به دلیل [[ویکیواژه:آزار و اذیت|آزار و اذیت]] سایر کاربران از ویرایش منع شدهاید.'
},
'uw-ipevadeblock': {
forAnonOnly: true,
nocreate: true,
reason: '[[ویکیواژه:قطع دسترسی|قطع دسترسی]]', // localized
summary: 'آدرس آیپی شما به دلیل [[ویکیواژه:قطع دسترسی|قطع دسترسی]] از ویرایش منع شدهاست.' // localized
},
'uw-lblock': {
autoblock: true,
expiry: 'infinity',
nocreate: true,
reason: 'Making [[ویکیواژه:تهدید قانونی ممنوع|تهدید قانونی ممنوع]]', // localized
summary: 'شما به دلیل [[ویکیواژه:تهدید قانونی ممنوع|تهدید قانونی ممنوع]] از ویرایش منع شدهاید.' // localized
},
'uw-nothereblock': {
autoblock: true,
expiry: 'infinity',
nocreate: true,
reason: 'روشن است که [[ویکیواژه:ما برای ساختن دانشنامه اینجا هستیم|برای ساختن دانشنامه اینجا نیستید.]]', // localized
forRegisteredOnly: true,
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا به نظر میرسد که شما در اینجا برای [[ویکیواژه:ما برای ساختن دانشنامه اینجا هستیم|ساختن دانشنامه]] نیستید' // localized
},
'uw-npblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: 'ایجاد مزخرفات ثبت اختراع یا سایر [[ویکیواژه:بیمعنی|صفحات نامناسب]]', // localized
summary: 'به دلیل ایجاد صفحات [[ویکیواژه:بیمعنی|بیمعنی]] از ویرایش منع شدهاید.' // localized
},
'uw-pablock': {
autoblock: true,
expiry: '31 hours',
nocreate: true,
reason: '[[ویکیواژه:حمله شخصی ممنوع|]] یا [[ویکیواژه:آزار و اذیت|]]', // localized
summary: 'شما به دلیل انجام [[ویکیواژه:حمله شخصی ممنوع|حملات شخصی]] به سایر کاربران از ویرایش منع شدهاید.' // localized
},
'uw-sblock': {
autoblock: true,
nocreate: true,
reason: 'استفاده از ویکیواژه به عنوان [[ویکیواژه:هرزنامه|هرزنامه]]', // localized
summary: 'به دلیل استفاده از ویکیواژه [[ویکیواژه:هرزنامه|هرزنامه]] از ویرایش منع شدهاید.' // localized
},
'uw-soablock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:هرزنامه]] / [[ویکیواژه:ویکیواژه چه چیزی نیست]]-تنها حساب', // localized
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا حساب شما فقط برای ایجاد [[ویکیواژه:هرزنامه|هرزنامه]] استفاده میشود.' // localized
},
'uw-socialmediablock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: 'استفاده از ویکیواژه به عنوان [[ویکیواژه:ویکیواژه چه چیزی نیست|یک وبلاگ، میزبان وب، سایت یا شبکههای اجتماعی]]', // localized
summary: 'شما به دلیل استفاده از صفحات کاربر و / یا مقاله به عنوان [[ویکیواژه:ویکیواژه چه چیزی نیست|وبلاگ، میزبان وب، سایت یا شبکههای اجتماعی]] از ویرایش منع شدهاید.' // localized
},
'uw-sockblock': {
autoblock: true,
forRegisteredOnly: true,
nocreate: true,
reason: 'سوءاستفاده از [[ویکیواژه:حساب زاپاس|چند حساب کاربری]]', // localized
summary: 'شما به دلیل سوء استفاده از [[ویکیواژه:حساب زاپاس|چند حساب کاربری]] ویرایش منع شدهاید ' // localized
},
'uw-softerblock': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-softerblock}} <!-- Promotional username, soft block -->',
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما این تصور را ایجاد میکند که این حساب نماینده یک گروه، سازمان یا وب سایت است' // localized
},
'uw-spamublock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: '{{Uw-spamublock}} <!-- Promotional username, promotional edits -->',
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا حساب شما فقط برای [[ویکیواژه:هرزنامه|هرزنامه یا تبلیغات]] استفاده میشود و [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما نقض خط مشی نام کاربری است.' // localized
},
'uw-spoablock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: '[[ویکیواژه:حساب زاپاس|حساب زاپاس]]', // localized
summary: 'این حساب به عنوان یک [[ویکیواژه:حساب زاپاس|حساب زاپاس]] ایجاد شده و براساس نقض خط مشی ویکی پد مسدود شده است.' // localized
},
'uw-talkrevoked': {
disabletalk: true,
reason: 'لغو دسترسی به صفحه بحث: استفاده نامناسب از صفحه بحث کاربر در حالی که مسدود شدهاست.', // localized
prependReason: true,
summary: 'دسترسی به صفحه گفتگوی کاربر غیرفعال شدهاست', // localized
useInitialOptions: true
},
'uw-ublock': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-ublock}} <!-- Username violation, soft block -->',
reasonParam: true,
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] ناقض قوانین است.' // localized
},
'uw-ublock-double': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-ublock-double}} <!-- Username closely resembles another user, soft block -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما بیش از حد به نام کاربری کاربر ویکیواژه دیگر شباهت دارد.' // localized
},
'uw-ucblock': {
autoblock: true,
expiry: '31 hours',
nocreate: true,
pageParam: true,
reason: ' [[راهنما:مقدمهای بر ارجاع/۱|افزودن محتوای بدون منبع]]', // localized
summary: 'به دلیل افزودن مداوم از [[راهنما:مقدمهای بر ارجاع/۱|محتوای بدون منبع]]، شما از ویرایش منع شدهاید.' // localized
},
'uw-uhblock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: '{{Uw-uhblock}} <!-- Username violation, hard block -->',
reasonParam: true,
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما نقض آشکار خط مشی نام کاربری است.' // localized
},
'uw-ublock-wellknown': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{بستن نام واقعی}} <!-- Username represents a well-known person, soft block -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما با نام یک فرد شناخته شده زنده مطابقت دارد.' // localized
},
'uw-uhblock-double': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: '{{Uw-ublock-double}} <!-- Username closely resembles another user, hard block -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا به نظر می رسد [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما نام یک کاربر دیگر ویکی پدیا را جعل میکند' // localized
},
'uw-upeblock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:فاشسازی مشارکت در برابر دستمزد]] in violation of the WMF [[WP:TOU|Terms of Use]]',
summary: 'شما به طور نامحدود به دلیل [[ویکیواژه:فاشسازی مشارکت در برابر دستمزد|فاشسازی مشارکت در برابر دستمزد]] از ویرایش منع شدهاید.' // localized
},
'uw-vaublock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reason: '{{Uw-vaublock}} <!-- Username violation, vandalism-only account -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:حساب ساختهشده برای خرابکاری|حساب شما]] برای خرابکاری ایجاد شدهاست و ناقض [[ویکیواژه:سیاست نام کاربری|سیاست نام کاربری]] است.' // localized
},
'uw-vblock': {
autoblock: true,
expiry: '31 hours',
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:خرابکاری|خرابکاری]]', // localized
summary: 'برای جلوگیری از بیشتر [[ویکیواژه:خرابکاری|خرابکاری]] شما از ویرایش منع شدهاید.' // localized
},
'uw-voablock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:حساب ساختهشده برای خرابکاری|حساب ساختهشده برای خرابکاری]]', // localized
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا حساب شما تنها برای [[ویکیواژه:حساب ساختهشده برای خرابکاری|خرابکاری]] استفاده میشود.' // localized
},
'zombie proxy': {
expiry: '1 month',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{زامبی پراکسی}}', // localized
sig: null
},
// Begin partial block templates, accessed in Twinkle.block.blockGroupsPartial
'uw-acpblock': {
autoblock: true,
expiry: '48 hours',
nocreate: true,
pageParam: false,
reasonParam: true,
reason: 'Misusing [[ویکیواژه:حساب زاپاس|حساب زاپاس]]', // localized
summary: 'به دلیل سوء استفاده از [[ویکیواژه:حساب زاپاس|چندین حساب]] از [[ویکیواژه:قطع دسترسی موردی|ایجاد حساب کاربری]] منع شدهاید.' // localized
},
'uw-acpblockindef': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: false,
reasonParam: true,
reason: 'سوء استفاده از [[ویکیواژه:حساب زاپاس|حساب زاپاس]]', // localized
summary: 'به دلیل سوء استفاده از [[ویکیواژه:حساب زاپاس|چندین حساب]] از [[ویکیواژه:قطع دسترسی موردی|ایجاد حساب کاربری]] منع شدهاید.' // localized
},
'uw-aepblock': {
autoblock: true,
nocreate: false,
pageParam: false,
reason: '[[WP:Arbitration enforcement|Arbitration enforcement]]',
reasonParam: true,
summary: 'به دلیل [[WP:Arbitration|نقض تصمیم داوری]] از ویرایش [[ویکیواژه:قطع دسترسی موردی|منع]] شده اید.' // localized
},
'uw-epblock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: false,
noemail: true,
pageParam: false,
reasonParam: true,
reason: '[[ویکیواژه:آزار و اذیت]] ایمیلی', // localized
summary: 'شما از ارسال ایمیل به ویرایشگران دیگر به دلیل [[ویکیواژه:آزار و اذیت|آزار و اذیت]] [[ویکیواژه:قطع دسترسی موردی|منع]] شدهاید' // localized
},
'uw-ewpblock': {
autoblock: true,
expiry: '24 hours',
nocreate: false,
pageParam: false,
reasonParam: true,
reason: '[[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]]', // localized
summary: 'شما از ویرایش مناطق خاصی از دانشنامه [[ویکیواژه:قطع دسترسی موردی|منع]] شدهاید تا از [[ویکیواژه:ویرایش اخلالگرانه|اختلال]] بیشتر ناشی از [[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]] جلوگیری شود.' // localized
},
'uw-pblock': {
autoblock: true,
expiry: '24 hours',
nocreate: false,
pageParam: false,
reasonParam: true,
summary: 'شما از ویرایش بخشهایی از دانشنامه [[ویکیواژه:قطع دسترسی مورد|منع]] شدهاید.' // localized
},
'uw-pblockindef': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: false,
pageParam: false,
reasonParam: true,
summary: 'شما از ویرایش بخشهایی از دانشنامه [[ویکیواژه:قطع دسترسی مورد|منع]] شدهاید.' // localized
}
};
Twinkle.block.transformBlockPresets = function twinkleblockTransformBlockPresets() {
// supply sensible defaults
$.each(Twinkle.block.blockPresetsInfo, function(preset, settings) {
settings.summary = settings.summary || settings.reason;
settings.sig = settings.sig !== undefined ? settings.sig : 'yes';
// despite this it's preferred that you use 'infinity' as the value for expiry
settings.indefinite = settings.indefinite || settings.expiry === 'infinity' || settings.expiry === 'infinite' || settings.expiry === 'indefinite' || settings.expiry === 'never';
if (!Twinkle.block.isRegistered && settings.indefinite) {
settings.expiry = '31 hours';
} else {
settings.expiry = settings.expiry || '31 hours';
}
Twinkle.block.blockPresetsInfo[preset] = settings;
});
};
// These are the groups of presets and defines the order in which they appear. For each list item:
// label: <string, the description that will be visible in the dropdown>
// value: <string, the key of a preset in blockPresetsInfo>
Twinkle.block.blockGroups = [
{
label: 'Common block reasons',
list: [
{ label: 'anonblock', value: 'anonblock' },
{ label: 'anonblock - likely a school', value: 'anonblock - school' },
{ label: 'school block', value: 'school block' },
{ label: 'Generic block (custom reason)', value: 'uw-block' }, // ends up being default for registered users
{ label: 'Generic block (custom reason) - IP', value: 'uw-ablock', selected: true }, // set only when blocking IP
{ label: 'Generic block (custom reason) - indefinite', value: 'uw-blockindef' },
{ label: 'Disruptive editing', value: 'uw-disruptblock' },
{ label: 'Inappropriate use of user talk page while blocked', value: 'uw-talkrevoked' },
{ label: 'Not here to build an encyclopedia', value: 'uw-nothereblock' },
{ label: 'Unsourced content', value: 'uw-ucblock' },
{ label: 'Vandalism', value: 'uw-vblock' },
{ label: 'Vandalism-only account', value: 'uw-voablock' }
]
},
{
label: 'Extended reasons',
list: [
{ label: 'Advertising', value: 'uw-adblock' },
{ label: 'Arbitration enforcement', value: 'uw-aeblock' },
{ label: 'Block evasion - IP', value: 'uw-ipevadeblock' },
{ label: 'BLP violations', value: 'uw-bioblock' },
{ label: 'Copyright violations', value: 'uw-copyrightblock' },
{ label: 'Creating nonsense pages', value: 'uw-npblock' },
{ label: 'Edit filter-related', value: 'uw-efblock' },
{ label: 'Edit warring', value: 'uw-ewblock' },
{ label: 'Generic block with talk page access revoked', value: 'uw-blocknotalk' },
{ label: 'Harassment', value: 'uw-hblock' },
{ label: 'Legal threats', value: 'uw-lblock' },
{ label: 'Personal attacks or harassment', value: 'uw-pablock' },
{ label: 'Possible compromised account', value: 'uw-compblock' },
{ label: 'Removal of content', value: 'uw-dblock' },
{ label: 'Sock puppetry (master)', value: 'uw-sockblock' },
{ label: 'Sock puppetry (puppet)', value: 'uw-spoablock' },
{ label: 'Social networking', value: 'uw-socialmediablock' },
{ label: 'Spam', value: 'uw-sblock' },
{ label: 'Spam/advertising-only account', value: 'uw-soablock' },
{ label: 'Unapproved bot', value: 'uw-botblock' },
{ label: 'Undisclosed paid editing', value: 'uw-upeblock' },
{ label: 'Violating the three-revert rule', value: 'uw-3block' }
]
},
{
label: 'Username violations',
list: [
{ label: 'Bot username', value: 'uw-botublock' },
{ label: 'Promotional username, hard block', value: 'uw-spamublock' },
{ label: 'Promotional username, soft block', value: 'uw-softerblock' },
{ label: 'Similar username soft block', value: 'uw-ublock-double' },
{ label: 'Username violation, soft block', value: 'uw-ublock' },
{ label: 'Username violation, hard block', value: 'uw-uhblock' },
{ label: 'Username impersonation hard block', value: 'uw-uhblock-double' },
{ label: 'Username represents a well-known person, soft block', value: 'uw-ublock-wellknown' },
{ label: 'Username represents a non-profit, soft block', value: 'uw-causeblock' },
{ label: 'Username violation, vandalism-only account', value: 'uw-vaublock' }
]
},
{
label: 'Templated reasons',
list: [
{ label: 'blocked proxy', value: 'blocked proxy' },
{ label: 'CheckUser block', value: 'CheckUser block', disabled: !Morebits.userIsInGroup('checkuser') },
{ label: 'checkuserblock-account', value: 'checkuserblock-account', disabled: !Morebits.userIsInGroup('checkuser') },
{ label: 'checkuserblock-wide', value: 'checkuserblock-wide', disabled: !Morebits.userIsInGroup('checkuser') },
{ label: 'colocationwebhost', value: 'colocationwebhost' },
{ label: 'oversightblock', value: 'oversightblock', disabled: !Morebits.userIsInGroup('oversight') },
// { label: 'rangeblock', value: 'rangeblock' }, // placeholder for when we add support for rangeblocks
{ label: 'spamblacklistblock', value: 'spamblacklistblock' },
{ label: 'tor', value: 'tor' },
{ label: 'webhostblock', value: 'webhostblock' },
{ label: 'zombie proxy', value: 'zombie proxy' }
]
}
];
Twinkle.block.blockGroupsPartial = [
{
label: 'Common partial block reasons',
list: [
{ label: 'Generic partial block (custom reason)', value: 'uw-pblock', selected: true },
{ label: 'Generic partial block (custom reason) - indefinite', value: 'uw-pblockindef' },
{ label: 'Edit warring', value: 'uw-ewpblock' }
]
},
{
label: 'Extended partial block reasons',
list: [
{ label: 'Arbitration enforcement', value: 'uw-aepblock' },
{ label: 'Email harassment', value: 'uw-epblock' },
{ label: 'Misusing multiple accounts', value: 'uw-acpblock' },
{ label: 'Misusing multiple accounts - indefinite', value: 'uw-acpblockindef' }
]
}
];
Twinkle.block.callback.filtered_block_groups = function twinkleblockCallbackFilteredBlockGroups(group, show_template) {
return $.map(group, function(blockGroup) {
var list = $.map(blockGroup.list, function(blockPreset) {
// only show uw-talkrevoked if reblocking
if (!Twinkle.block.currentBlockInfo && blockPreset.value === 'uw-talkrevoked') {
return;
}
var blockSettings = Twinkle.block.blockPresetsInfo[blockPreset.value];
var registrationRestrict = blockSettings.forRegisteredOnly ? Twinkle.block.isRegistered : blockSettings.forAnonOnly ? !Twinkle.block.isRegistered : true;
if (!(blockSettings.templateName && show_template) && registrationRestrict) {
var templateName = blockSettings.templateName || blockPreset.value;
return {
label: (show_template ? '{{' + templateName + '}}: ' : '') + blockPreset.label,
value: blockPreset.value,
data: [{
name: 'template-name',
value: templateName
}],
selected: !!blockPreset.selected,
disabled: !!blockPreset.disabled
};
}
});
if (list.length) {
return {
label: blockGroup.label,
list: list
};
}
});
};
Twinkle.block.callback.change_preset = function twinkleblockCallbackChangePreset(e) {
var key = e.target.form.preset.value;
if (!key) {
return;
}
e.target.form.template.value = Twinkle.block.blockPresetsInfo[key].templateName || key;
Twinkle.block.callback.update_form(e, Twinkle.block.blockPresetsInfo[key]);
Twinkle.block.callback.change_template(e);
};
Twinkle.block.callback.change_expiry = function twinkleblockCallbackChangeExpiry(e) {
var expiry = e.target.form.expiry;
if (e.target.value === 'custom') {
Morebits.quickForm.setElementVisibility(expiry.parentNode, true);
} else {
Morebits.quickForm.setElementVisibility(expiry.parentNode, false);
expiry.value = e.target.value;
}
};
Twinkle.block.seeAlsos = [];
Twinkle.block.callback.toggle_see_alsos = function twinkleblockCallbackToggleSeeAlso() {
var reason = this.form.reason.value.replace(
new RegExp('( <!--|;) ' + 'see also ' + Twinkle.block.seeAlsos.join(' and ') + '( -->)?'), ''
);
Twinkle.block.seeAlsos = Twinkle.block.seeAlsos.filter(function(el) {
return el !== this.value;
}.bind(this));
if (this.checked) {
Twinkle.block.seeAlsos.push(this.value);
}
var seeAlsoMessage = Twinkle.block.seeAlsos.join(' and ');
if (!Twinkle.block.seeAlsos.length) {
this.form.reason.value = reason;
} else if (reason.indexOf('{{') !== -1) {
this.form.reason.value = reason + ' <!-- see also ' + seeAlsoMessage + ' -->';
} else {
this.form.reason.value = reason + '; see also ' + seeAlsoMessage;
}
};
Twinkle.block.callback.update_form = function twinkleblockCallbackUpdateForm(e, data) {
var form = e.target.form, expiry = data.expiry;
// don't override original expiry if useInitialOptions is set
if (!data.useInitialOptions) {
if (Date.parse(expiry)) {
expiry = new Date(expiry).toGMTString();
form.expiry_preset.value = 'custom';
} else {
form.expiry_preset.value = data.expiry || 'custom';
}
form.expiry.value = expiry;
if (form.expiry_preset.value === 'custom') {
Morebits.quickForm.setElementVisibility(form.expiry.parentNode, true);
} else {
Morebits.quickForm.setElementVisibility(form.expiry.parentNode, false);
}
}
// boolean-flipped options, more at [[mw:API:Block]]
data.disabletalk = data.disabletalk !== undefined ? data.disabletalk : false;
data.hardblock = data.hardblock !== undefined ? data.hardblock : false;
// disable autoblock if blocking a bot
if (Twinkle.block.isRegistered && relevantUserName.search(/bot\b/i) > 0) {
data.autoblock = false;
}
$(form.field_block_options).find(':checkbox').each(function(i, el) {
// don't override original options if useInitialOptions is set
if (data.useInitialOptions && data[el.name] === undefined) {
return;
}
var check = data[el.name] === '' || !!data[el.name];
$(el).prop('checked', check);
});
if (data.prependReason && data.reason) {
form.reason.value = data.reason + '; ' + form.reason.value;
} else {
form.reason.value = data.reason || '';
}
};
Twinkle.block.callback.change_template = function twinkleblockcallbackChangeTemplate(e) {
var form = e.target.form, value = form.template.value, settings = Twinkle.block.blockPresetsInfo[value];
if (!$(form).find('[name=actiontype][value=block]').is(':checked')) {
if (settings.indefinite || settings.nonstandard) {
if (Twinkle.block.prev_template_expiry === null) {
Twinkle.block.prev_template_expiry = form.template_expiry.value || '';
}
form.template_expiry.parentNode.style.display = 'none';
form.template_expiry.value = 'indefinite';
} else if (form.template_expiry.parentNode.style.display === 'none') {
if (Twinkle.block.prev_template_expiry !== null) {
form.template_expiry.value = Twinkle.block.prev_template_expiry;
Twinkle.block.prev_template_expiry = null;
}
form.template_expiry.parentNode.style.display = 'block';
}
if (Twinkle.block.prev_template_expiry) {
form.expiry.value = Twinkle.block.prev_template_expiry;
}
Morebits.quickForm.setElementVisibility(form.notalk.parentNode, !settings.nonstandard);
Morebits.quickForm.setElementVisibility(form.noemail_template.parentNode, $(form).find('[name=actiontype][value=partial]').is(':checked') && !$(form).find('[name=actiontype][value=block]').is(':checked'));
Morebits.quickForm.setElementVisibility(form.nocreate_template.parentNode, $(form).find('[name=actiontype][value=partial]').is(':checked') && !$(form).find('[name=actiontype][value=block]').is(':checked'));
} else {
Morebits.quickForm.setElementVisibility(
form.blank_duration.parentNode,
!settings.indefinite && !settings.nonstandard
);
}
Morebits.quickForm.setElementVisibility(form.article.parentNode, !!settings.pageParam);
Morebits.quickForm.setElementVisibility(form.block_reason.parentNode, !!settings.reasonParam);
// Partial block
Morebits.quickForm.setElementVisibility(form.area.parentNode, $(form).find('[name=actiontype][value=partial]').is(':checked') && !$(form).find('[name=actiontype][value=block]').is(':checked'));
form.root.previewer.closePreview();
};
Twinkle.block.prev_template_expiry = null;
Twinkle.block.prev_block_reason = null;
Twinkle.block.prev_article = null;
Twinkle.block.prev_reason = null;
Twinkle.block.callback.preview = function twinkleblockcallbackPreview(form) {
var params = {
article: form.article.value,
blank_duration: form.blank_duration ? form.blank_duration.checked : false,
disabletalk: form.disabletalk.checked || (form.notalk ? form.notalk.checked : false),
expiry: form.template_expiry ? form.template_expiry.value : form.expiry.value,
hardblock: Twinkle.block.isRegistered ? form.autoblock.checked : form.hardblock.checked,
indefinite: (/indef|infinit|never|\*|max/).test(form.template_expiry ? form.template_expiry.value : form.expiry.value),
reason: form.block_reason.value,
template: form.template.value,
partial: $(form).find('[name=actiontype][value=partial]').is(':checked'),
pagerestrictions: $(form.pagerestrictions).val() || [],
namespacerestrictions: $(form.namespacerestrictions).val() || [],
noemail: form.noemail.checked || (form.noemail_template ? form.noemail_template.checked : false),
nocreate: form.nocreate.checked || (form.nocreate_template ? form.nocreate_template.checked : false),
area: form.area.value
};
var templateText = Twinkle.block.callback.getBlockNoticeWikitext(params);
form.previewer.beginRender(templateText, 'User_talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username
};
Twinkle.block.callback.evaluate = function twinkleblockCallbackEvaluate(e) {
var $form = $(e.target),
toBlock = $form.find('[name=actiontype][value=block]').is(':checked'),
toWarn = $form.find('[name=actiontype][value=template]').is(':checked'),
toPartial = $form.find('[name=actiontype][value=partial]').is(':checked'),
blockoptions = {}, templateoptions = {};
Twinkle.block.callback.saveFieldset($form.find('[name=field_block_options]'));
Twinkle.block.callback.saveFieldset($form.find('[name=field_template_options]'));
blockoptions = Twinkle.block.field_block_options;
templateoptions = Twinkle.block.field_template_options;
templateoptions.disabletalk = !!(templateoptions.disabletalk || blockoptions.disabletalk);
templateoptions.hardblock = !!blockoptions.hardblock;
delete blockoptions.expiry_preset; // remove extraneous
// Partial API requires this to be gone, not false or 0
if (toPartial) {
blockoptions.partial = templateoptions.partial = true;
}
templateoptions.pagerestrictions = $form.find('[name=pagerestrictions]').val() || [];
templateoptions.namespacerestrictions = $form.find('[name=namespacerestrictions]').val() || [];
// Format for API here rather than in saveFieldset
blockoptions.pagerestrictions = templateoptions.pagerestrictions.join('|');
blockoptions.namespacerestrictions = templateoptions.namespacerestrictions.join('|');
// use block settings as warn options where not supplied
templateoptions.summary = templateoptions.summary || blockoptions.reason;
templateoptions.expiry = templateoptions.template_expiry || blockoptions.expiry;
if (toBlock) {
if (blockoptions.partial) {
if (blockoptions.disabletalk && blockoptions.namespacerestrictions.indexOf('3') === -1) {
return alert('Partial blocks cannot prevent talk page access unless also restricting them from editing User talk space!');
}
if (!blockoptions.namespacerestrictions && !blockoptions.pagerestrictions) {
if (!blockoptions.noemail && !blockoptions.nocreate) { // Blank entries technically allowed [[phab:T208645]]
return alert('No pages or namespaces were selected, nor were email or account creation restrictions applied; please select at least one option to apply a partial block!');
} else if (!confirm('You are about to block with no restrictions on page or namespace editing, are you sure you want to proceed?')) {
return;
}
}
}
if (!blockoptions.expiry) {
return alert('Please provide an expiry!');
}
if (!blockoptions.reason) {
return alert('Please provide a reason for the block!');
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(e.target);
var statusElement = new Morebits.status('Executing block');
blockoptions.action = 'block';
blockoptions.user = mw.config.get('wgRelevantUserName');
// boolean-flipped options
blockoptions.anononly = blockoptions.hardblock ? undefined : true;
blockoptions.allowusertalk = blockoptions.disabletalk ? undefined : true;
/*
Check if block status changed while processing the form.
There's a lot to consider here. list=blocks provides the
current block status, but there are at least two issues with
relying on it. First, the id doesn't update on a reblock,
meaning the individual parameters need to be compared. This
can be done roughly with JSON.stringify - we can thankfully
rely on order from the server, although sorting would be
fine if not - but falsey values are problematic and is
non-ideal. More importantly, list=blocks won't indicate if a
non-blocked user is blocked then unblocked. This should be
exceedingy rare, but regardless, we thus need to check
list=logevents, which has a nicely updating logid
parameter. We can't rely just on that, though, since it
doesn't account for blocks that have expired on their own.
As such, we use both. Using some ternaries, the logid
variables are false if there's no logevents, so if they
aren't equal we defintely have a changed entry (send
confirmation). If they are equal, then either the user was
never blocked (the block statuses will be equal, no
confirmation) or there's no new block, in which case either
a block expired (different statuses, confirmation) or the
same block is still active (same status, no confirmation).
*/
api.get({
format: 'json',
action: 'query',
list: 'blocks|logevents',
letype: 'block',
lelimit: 1,
letitle: 'User:' + blockoptions.user,
bkusers: blockoptions.user
}).then(function(data) {
var block = data.query.blocks[0];
var logevents = data.query.logevents[0];
var logid = data.query.logevents.length ? logevents.logid : false;
if (logid !== Twinkle.block.blockLogId || !!block !== !!Twinkle.block.currentBlockInfo) {
var message = 'The block status of ' + mw.config.get('wgRelevantUserName') + ' has changed. ';
if (block) {
message += 'New status: ';
} else {
message += 'Last entry: ';
}
var logExpiry = '';
if (logevents.params.duration) {
if (logevents.params.duration === 'infinity') {
logExpiry = 'indefinitely';
} else {
var expiryDate = new Morebits.date(logevents.params.expiry);
logExpiry += (expiryDate.isBefore(new Date()) ? ', expired ' : ' until ') + expiryDate.calendar();
}
} else { // no duration, action=unblock, just show timestamp
logExpiry = ' ' + new Morebits.date(logevents.timestamp).calendar();
}
message += Morebits.string.toUpperCaseFirstChar(logevents.action) + 'ed by ' + logevents.user + logExpiry +
' for "' + logevents.comment + '". Do you want to override with your settings?';
if (!confirm(message)) {
Morebits.status.info('اعمال قطع دسترسی', 'توسط کاربر لغو شد'); // localized
return;
}
blockoptions.reblock = 1; // Writing over a block will fail otherwise
}
// execute block
blockoptions.token = mw.user.tokens.get('csrfToken');
var mbApi = new Morebits.wiki.api('اعمال قطع دسترسی', blockoptions, function() { // localized
statusElement.info('کامل شد'); // localized
if (toWarn) {
Twinkle.block.callback.issue_template(templateoptions);
}
});
mbApi.post();
});
} else if (toWarn) {
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(e.target);
Twinkle.block.callback.issue_template(templateoptions);
} else {
return alert('Please give Twinkle something to do!');
}
};
Twinkle.block.callback.issue_template = function twinkleblockCallbackIssueTemplate(formData) {
var userTalkPage = 'User_talk:' + mw.config.get('wgRelevantUserName');
var params = $.extend(formData, {
messageData: Twinkle.block.blockPresetsInfo[formData.template],
reason: Twinkle.block.field_template_options.block_reason,
disabletalk: Twinkle.block.field_template_options.notalk,
noemail: Twinkle.block.field_template_options.noemail_template,
nocreate: Twinkle.block.field_template_options.nocreate_template
});
Morebits.wiki.actionCompleted.redirect = userTalkPage;
Morebits.wiki.actionCompleted.notice = 'عمل انجام شد، بارگیری صفحهٔ بحث کاربر تا چند ثانیهٔ دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'User talk page modification');
wikipedia_page.setCallbackParameters(params);
wikipedia_page.setFollowRedirect(true);
wikipedia_page.load(Twinkle.block.callback.main);
};
Twinkle.block.callback.getBlockNoticeWikitext = function(params) {
var text = '{{', settings = Twinkle.block.blockPresetsInfo[params.template];
if (!settings.nonstandard) {
text += 'subst:' + params.template;
if (params.article && settings.pageParam) {
text += '|page=' + params.article;
}
if (!/te?mp|^\s*$|min/.exec(params.expiry)) {
if (params.indefinite) {
text += '|indef=yes';
} else if (!params.blank_duration) {
text += '|time=' + params.expiry;
}
}
if (!Twinkle.block.isRegistered && !params.hardblock) {
text += '|anon=yes';
}
if (params.reason) {
text += '|reason=' + params.reason;
}
if (params.disabletalk) {
text += '|notalk=yes';
}
// Currently, all partial block templates are "standard"
// Building the template, however, takes a fair bit of logic
if (params.partial) {
if (params.pagerestrictions.length || params.namespacerestrictions.length) {
var makeSentence = function (array) {
if (array.length < 3) {
return array.join(' and ');
}
var last = array.pop();
return array.join(', ') + ', and ' + last;
};
text += '|area=' + (params.indefinite ? 'certain ' : 'from certain ');
if (params.pagerestrictions.length) {
text += 'pages (' + makeSentence(params.pagerestrictions.map(function(p) {
return '[[:' + p + ']]';
}));
text += params.namespacerestrictions.length ? ') and certain ' : ')';
}
if (params.namespacerestrictions.length) {
// 1 => Talk, 2 => User, etc.
var namespaceNames = params.namespacerestrictions.map(function(id) {
return menuFormattedNamespaces[id];
});
text += '[[ویکیواژه:فضای نام]] (' + makeSentence(namespaceNames) + ')'; // localized
}
} else if (params.area) {
text += '|area=' + params.area;
} else {
if (params.noemail) {
text += '|email=yes';
}
if (params.nocreate) {
text += '|accountcreate=yes';
}
}
}
} else {
text += params.template;
}
if (settings.sig) {
text += '|sig=' + settings.sig;
}
return text + '}}';
};
Twinkle.block.callback.main = function twinkleblockcallbackMain(pageobj) {
var text = pageobj.getPageText(),
params = pageobj.getCallbackParameters(),
messageData = params.messageData,
date = new Morebits.date(pageobj.getLoadTime());
var dateHeaderRegex = date.monthHeaderRegex(), dateHeaderRegexLast, dateHeaderRegexResult;
while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) {
dateHeaderRegexResult = dateHeaderRegexLast;
}
// If dateHeaderRegexResult is null then lastHeaderIndex is never checked. If it is not null but
// \n== is not found, then the date header must be at the very start of the page. lastIndexOf
// returns -1 in this case, so lastHeaderIndex gets set to 0 as desired.
var lastHeaderIndex = text.lastIndexOf('\n==') + 1;
if (text.length > 0) {
text += '\n\n';
}
params.indefinite = (/indef|infinit|never|\*|max/).test(params.expiry);
if (Twinkle.getPref('blankTalkpageOnIndefBlock') && params.template !== 'uw-lblock' && params.indefinite) {
Morebits.status.info('اطلاعرسانی', 'صفحهٔ بحث کاربر به درخواست شما خالی شد و یک بخش با عنوان سطح ۲ و تاریخ امروز در آن افزوده شد'); // localiezd
text = date.monthHeader() + '\n';
} else if (!dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex) {
Morebits.status.info('اطلاعرسانی', 'یک بخش جدید با عنوان سطح ۲ و تاریخ امروز در صفحهٔ بحث افزوده خواهد شد، چرا که بخش مشابهی برای ماه جاری یافت نشد'); // localized
text += date.monthHeader() + '\n';
}
params.expiry = typeof params.template_expiry !== 'undefined' ? params.template_expiry : params.expiry;
text += Twinkle.block.callback.getBlockNoticeWikitext(params);
// build the edit summary
var summary = messageData.summary;
if (messageData.suppressArticleInSummary !== true && params.article) {
summary += ' بر [[:' + params.article + ']]'; // localized
}
summary += '.' + Twinkle.getPref('summaryAd');
pageobj.setPageText(text);
pageobj.setEditSummary(summary);
pageobj.setWatchlist(Twinkle.getPref('watchWarnings'));
pageobj.save();
};
})(jQuery);
// </nowiki>
/* ===== friendlywelcome.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** friendlywelcome.js: Welcome module
****************************************
* Mode of invocation: Tab ("Wel"), or from links on diff pages
* Active on: Any page with relevant user name (userspace,
* contribs, etc.) and diff pages
*/
Twinkle.welcome = function friendlywelcome() {
if (mw.util.getParamValue('friendlywelcome')) {
if (mw.util.getParamValue('friendlywelcome') === 'auto') {
Twinkle.welcome.auto();
} else {
Twinkle.welcome.semiauto();
}
} else {
Twinkle.welcome.normal();
}
};
Twinkle.welcome.auto = function() {
if (mw.util.getParamValue('action') !== 'edit') {
// userpage not empty, aborting auto-welcome
return;
}
Twinkle.welcome.welcomeUser();
};
Twinkle.welcome.semiauto = function() {
Twinkle.welcome.callback(mw.config.get('wgRelevantUserName'));
};
Twinkle.welcome.normal = function() {
if (mw.util.getParamValue('diff')) {
// check whether the contributors' talk pages exist yet
var $oList = $('#mw-diff-otitle2').find('span.mw-usertoollinks a.new:contains(talk)').first();
var $nList = $('#mw-diff-ntitle2').find('span.mw-usertoollinks a.new:contains(talk)').first();
if ($oList.length > 0 || $nList.length > 0) {
var spanTag = function(color, content) {
var span = document.createElement('span');
span.style.color = color;
span.appendChild(document.createTextNode(content));
return span;
};
var welcomeNode = document.createElement('strong');
var welcomeLink = document.createElement('a');
welcomeLink.appendChild(spanTag('Black', '['));
welcomeLink.appendChild(spanTag('Goldenrod', 'خوشامد')); // localized
welcomeLink.appendChild(spanTag('Black', ']'));
welcomeNode.appendChild(welcomeLink);
if ($oList.length > 0) {
var oHref = $oList.attr('href');
var oWelcomeNode = welcomeNode.cloneNode(true);
oWelcomeNode.firstChild.setAttribute('href', oHref + '&' + $.param({
'friendlywelcome': Twinkle.getPref('quickWelcomeMode') === 'auto' ? 'auto' : 'norm',
'vanarticle': Morebits.pageNameNorm
}));
$oList[0].parentNode.parentNode.appendChild(document.createTextNode(' '));
$oList[0].parentNode.parentNode.appendChild(oWelcomeNode);
}
if ($nList.length > 0) {
var nHref = $nList.attr('href');
var nWelcomeNode = welcomeNode.cloneNode(true);
nWelcomeNode.firstChild.setAttribute('href', nHref + '&' + $.param({
'friendlywelcome': Twinkle.getPref('quickWelcomeMode') === 'auto' ? 'auto' : 'norm',
'vanarticle': Morebits.pageNameNorm
}));
$nList[0].parentNode.parentNode.appendChild(document.createTextNode(' '));
$nList[0].parentNode.parentNode.appendChild(nWelcomeNode);
}
}
}
if (mw.config.get('wgRelevantUserName')) {
Twinkle.addPortletLink(function() {
Twinkle.welcome.callback(mw.config.get('wgRelevantUserName'));
}, 'خوشامد', 'friendly-welcome', 'خوشامدگویی به کاربر'); // localized
}
};
Twinkle.welcome.welcomeUser = function welcomeUser() {
Morebits.status.init(document.getElementById('mw-content-text'));
$('#catlinks').remove();
var params = {
value: Twinkle.getPref('quickWelcomeTemplate'),
article: mw.util.getParamValue('vanarticle') || '',
mode: 'auto'
};
var userTalkPage = mw.config.get('wgFormattedNamespaces')[3] + ':' + mw.config.get('wgRelevantUserName');
Morebits.wiki.actionCompleted.redirect = userTalkPage;
Morebits.wiki.actionCompleted.notice = 'خوشامدگویی انجام شد، در حال بارگزاری دوبارهٔ صفحه بحث تا چند ثانیهٔ دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'ویرایش صفحهٔ بحث کاربر'); // localized
wikipedia_page.setFollowRedirect(true);
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.welcome.callbacks.main);
};
Twinkle.welcome.callback = function friendlywelcomeCallback(uid) {
if (uid === mw.config.get('wgUserName') && !confirm('آیا از خوشامدگویی به خودتان مطمئن هستید؟...')) { // localized
return;
}
var Window = new Morebits.simpleWindow(600, 420);
Window.setTitle('خوشامدگویی به کاربر'); // localized
Window.setScriptName('توینکل'); // localiezd
// Window.addFooterLink('Welcoming Committee', 'WP:WC'); // localized [removed]
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#welcome'); // localized
var form = new Morebits.quickForm(Twinkle.welcome.callback.evaluate);
form.append({
type: 'select',
name: 'type',
label: 'نوع خوشامدگویی: ',
event: Twinkle.welcome.populateWelcomeList,
list: [
{ type: 'option', value: 'standard', label: 'خوشامدهای استاندارد', selected: !mw.util.isIPAddress(mw.config.get('wgRelevantUserName')) }, // localized
{ type: 'option', value: 'anonymous', label: 'خوشامد به کاربران ناشناس', selected: mw.util.isIPAddress(mw.config.get('wgRelevantUserName')) } // , // localized
// { type: 'option', value: 'wikiProject', label: 'WikiProject welcomes' }, // localized [removed]
// { type: 'option', value: 'nonEnglish', label: 'Non-English welcomes' } // localiezd [removed]
]
});
form.append({
type: 'div',
id: 'welcomeWorkArea',
className: 'morebits-scrollbox'
});
form.append({
type: 'input',
name: 'article',
label: '* مقالهٔ پیوند شده (در صورت پشتیبانی توسط الگو):', // localized
value: mw.util.getParamValue('vanarticle') || '',
tooltip: 'در صورت پشتیبانی توسط الگو، میتوان یک مقاله را از درون پیام خوشامد پیوند داد. برای اینکه به هیچ مقالهای پیوند داده نشود، جعبه را خالی بگذارید. الگوهایی که از پیونددهی به یک مقاله پشتیبانی میکنند با یک ستاره علامتگذاری شدهاند.' // localized
});
var previewlink = document.createElement('a');
$(previewlink).click(function() {
Twinkle.welcome.callbacks.preview(result); // |result| is defined below
});
previewlink.style.cursor = 'pointer';
previewlink.textContent = 'پیشنمایش'; // localized
form.append({ type: 'div', name: 'welcomepreview', label: [ previewlink ] });
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
// initialize the welcome list
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.type.dispatchEvent(evt);
};
Twinkle.welcome.populateWelcomeList = function(e) {
var type = e.target.value;
var container = new Morebits.quickForm.element({ type: 'fragment' });
if ((type === 'standard' || type === 'anonymous') && Twinkle.getPref('customWelcomeList').length) {
container.append({ type: 'header', label: 'الگوهای خوشامد سفارشی' }); // localized
container.append({
type: 'radio',
name: 'template',
list: Twinkle.getPref('customWelcomeList'),
event: function() {
e.target.form.article.disabled = false;
}
});
}
var sets = Twinkle.welcome.templates[type];
$.each(sets, function(label, templates) {
container.append({ type: 'header', label: label });
container.append({
type: 'radio',
name: 'template',
list: $.map(templates, function(properties, template) {
return {
value: template,
label: '{{' + template + '}}: ' + properties.description + (properties.linkedArticle ? '\u00A0*' : ''), // U+00A0 NO-BREAK SPACE
tooltip: properties.tooltip // may be undefined
};
}),
event: function(ev) {
ev.target.form.article.disabled = !templates[ev.target.value].linkedArticle;
}
});
});
var rendered = container.render();
$(e.target.form).find('div#welcomeWorkArea').empty().append(rendered);
var firstRadio = e.target.form.template[0];
firstRadio.checked = true;
var vals = sets[Object.keys(sets)[0]];
e.target.form.article.disabled = vals[firstRadio.value] ? !vals[firstRadio.value].linkedArticle : true;
};
// A list of welcome templates and their properties and syntax
// The four fields that are available are "description", "linkedArticle", "syntax", and "tooltip".
// The three magic words that can be used in the "syntax" field are:
// - $USERNAME$ - replaced by the welcomer's username, depending on user's preferences
// - $ARTICLE$ - replaced by an article name, if "linkedArticle" is true
// - $HEADER$ - adds a level 2 header (most templates already include this)
Twinkle.welcome.templates = {
'standard': {
'الگوی خوشامد': { // localized
'خوشامد': { // localized
description: 'خوشامد استاندارد', // localized
syntax: '{{subst:خوشامد}} ~~~~' // localized
}
}
},
'anonymous': {
'الگوی خوشامد': { // localized
'خوشامد': { // localized
description: 'خوشامد استاندارد', // localized
syntax: '{{subst:خوشامد}} ~~~~' // localized
}
}
}
};
Twinkle.welcome.getTemplateWikitext = function(type, template, article) {
// the iteration is required as the type=standard has two groups
var properties;
$.each(Twinkle.welcome.templates[type], function(label, templates) {
properties = templates[template];
if (properties) {
return false; // break
}
});
if (properties) {
return properties.syntax.
replace('$USERNAME$', Twinkle.getPref('insertUsername') ? mw.config.get('wgUserName') : '').
replace('$ARTICLE$', article ? article : '').
replace(/\$HEADER\$\s*/, '== Welcome ==\n\n').
replace('$EXTRA$', ''); // EXTRA is not implemented yet
}
return '{{subst:' + template + (article ? '|art=' + article : '') + '}}' +
(Twinkle.getPref('customWelcomeSignature') ? ' ~~~~' : '');
};
Twinkle.welcome.callbacks = {
preview: function(form) {
var previewDialog = new Morebits.simpleWindow(750, 400);
previewDialog.setTitle('پیشنمایش الگوی خوشامد'); // localized
previewDialog.setScriptName('خوشامد به کاربر'); // localized
previewDialog.setModality(true);
var previewdiv = document.createElement('div');
previewdiv.style.marginLeft = previewdiv.style.marginRight = '0.5em';
previewdiv.style.fontSize = 'small';
previewDialog.setContent(previewdiv);
var previewer = new Morebits.wiki.preview(previewdiv);
previewer.beginRender(Twinkle.welcome.getTemplateWikitext(form.type.value, form.getChecked('template'), form.article.value), 'User talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username
var submit = document.createElement('input');
submit.setAttribute('type', 'submit');
submit.setAttribute('value', 'Close');
previewDialog.addContent(submit);
previewDialog.display();
$(submit).click(function() {
previewDialog.close();
});
},
main: function(pageobj) {
var params = pageobj.getCallbackParameters();
var text = pageobj.getPageText();
// abort if mode is auto and form is not empty
if (pageobj.exists() && params.mode === 'auto') {
Morebits.status.info('Warning', 'صفحهٔ بحث کاربر خالی نیست؛ در حال چشمپوشی از خوشامدگویی خودکار'); // localized
Morebits.wiki.actionCompleted.event();
return;
}
var welcomeText = Twinkle.welcome.getTemplateWikitext(params.type, params.value, params.article);
if (Twinkle.getPref('topWelcomes')) {
text = welcomeText + '\n\n' + text;
} else {
text += '\n' + welcomeText;
}
var summaryText = 'به ویکیواژه خوشآمدید!'; // localized
pageobj.setPageText(text);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchWelcomes'));
pageobj.setCreateOption('recreate');
pageobj.save();
}
};
Twinkle.welcome.callback.evaluate = function friendlywelcomeCallbackEvaluate(e) {
var form = e.target;
var params = {
type: form.type.value,
value: form.getChecked('template'),
article: form.article.value,
mode: 'manual'
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
var userTalkPage = mw.config.get('wgFormattedNamespaces')[3] + ':' + mw.config.get('wgRelevantUserName');
Morebits.wiki.actionCompleted.redirect = userTalkPage;
Morebits.wiki.actionCompleted.notice = 'خوشامدگویی انجام شد، در حال بارگزاری دوبارهٔ صفحهٔ بحث تا چند لحظه دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'User talk page modification');
wikipedia_page.setFollowRedirect(true);
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.welcome.callbacks.main);
};
})(jQuery);
// </nowiki>
/* ===== friendlyshared.js ===== */
// <nowiki>
(function($) { // eslint-disable-line no-unused-vars
/*
****************************************
*** friendlyshared.js: Shared IP tagging module
****************************************
* Mode of invocation: Tab ("Shared")
* Active on: Existing IP user talk pages
*/
Twinkle.shared = function friendlyshared() {
if (mw.config.get('wgNamespaceNumber') === 3 && mw.util.isIPAddress(mw.config.get('wgTitle'))) {
var username = mw.config.get('wgRelevantUserName');
Twinkle.addPortletLink(function() {
Twinkle.shared.callback(username);
}, 'آیپی اشتراکی', 'friendly-shared', 'برچسبزدن آیپیهای اشتراکی'); // localized
}
};
Twinkle.shared.callback = function friendlysharedCallback() {
var Window = new Morebits.simpleWindow(600, 420);
Window.setTitle('برچسب زدن نشانیهای آیپی اشتراکی'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#shared'); // localiezd
var form = new Morebits.quickForm(Twinkle.shared.callback.evaluate);
var div = form.append({
type: 'div',
id: 'sharedip-templatelist',
className: 'morebits-scrollbox'
}
);
div.append({ type: 'header', label: 'الگوهای نشانی آیپی اشتراکی' }); // localized
div.append({ type: 'radio', name: 'shared', list: Twinkle.shared.standardList,
event: function(e) {
Twinkle.shared.callback.change_shared(e);
e.stopPropagation();
}
});
var org = form.append({ type: 'field', label: 'سایر جزئیات (اختیاری) را پر کنید و روی دکمهٔ پایین فرم کلیک کنید' }); // localized
org.append({
type: 'input',
name: 'organization',
label: 'مالک/گردانندهٔ نشانی آیپی', // localized
disabled: true,
tooltip: 'میتوانید به صورت اختیاری نام سازمانی را که مالک/گردانندهٔ نشانی آیپی است، وارد کنید. در صورت لزوم میتوانید از ویکیکد استفاده کنید.' // localized
}
);
org.append({
type: 'input',
name: 'host',
label: 'نام میزبان (اختیاری)', // localized
disabled: true,
tooltip: 'نام میزبان (نظیر proxy.example.com) میتواند به صورت اختیاری در اینجا وارد شود و با الگو پیوند خواهد یافت.' // localized
}
);
org.append({
type: 'input',
name: 'contact',
label: 'اطلاعات تماس (فقط در صورت درخواست)', // localized
disabled: true,
tooltip: 'میتوانید به طور اختیاری برخی از اطلاعات تماس را برای آن سازمان وارد کنید. از این پارامتر فقط در صورتی استفاده کنید که سازمان بهطور خاص درخواست کرده است که آن را اضافه کنید. در صورت لزوم میتوانید از ویکیکد استفاده کنید.' // localized
}
);
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
};
Twinkle.shared.standardList = [
{
label: '{{آیپی مشترک}}: الگوی استاندارد نشانی آیپی مشترک', // localized
value: 'آیپی مشترک', // localized
tooltip: 'الگوی صفحه بحث کاربر آیپی که اطلاعات مفیدی را برای کاربران آیپی و کسانی که مایل به هشدار به آنها یا بستن یا تحریم آنها هستند نشان میدهد' // localized
},
{
label: '{{آیپی مشترک آموزشی}}: الگوی اصلاحشده نشانی آیپی مشترک برای مؤسسات آموزشی', // localized
value: 'آیپی مشترک آموزشی' // localized
},
{
label: '{{آیپی مشترک شرکتی}}: الگوی اصلاحشده نشانی آیپی مشترک برای مشاغل', // localized
value: 'آیپی مشترک آموزشی' // localized
},
{
label: '{{نشانی آیپی مشترک (عمومی)}}: الگوی اصلاحشده نشانی آیپی مشترک برای پایانههای عمومی', // localized
value: 'آیپی مشترک (عمومی)' // localized
},
{
label: '{{آیپی مشترک دولتی}}: الگوی اصلاحشده نشانی آیپی مشترک برای آژانسهای دولتی یا دفترهای خدماتی', // localized
value: 'آیپی مشترک دولتی' // localized
},
{
label: '{{آیپی پویا}}: الگوی اصلاحشده نشانی آیپی مشترک برای سازمانهایی با آدرسدهی پویا', // localized
value: 'آیپی پویا' // localized
},
{
label: '{{آیپی ایستا}}: الگوی اصلاحشده نشانی آیپی مشترک برای آدرسهای آیپی ثابت', // localized
value: 'آیپی ایستا' // localized
},
{
label: '{{آیاسپی}}: الگوی اصلاحشده نشانی آیپی مشترک برای سازمانهای ISP (به طور خاص پراکسی)', // localized
value: 'آیاسپی' // localized
},
{
label: '{{آیپی تلفن همراه}}:الگوی اصلاحشده نشانی آیپی مشترک برای شرکتهای تلفن همراه و مشتریان آنها', // localized
value: 'آیپی تلفن همراه' // localized
},
{
label: '{{Whois}}: الگوی آدرسهای آیپی که نیاز به نظارت دارند، اما معلوم نیست که ایستا، پویا یا مشترک هستند', // localized
value: 'Whois'
}
];
Twinkle.shared.callback.change_shared = function friendlysharedCallbackChangeShared(e) {
e.target.form.contact.disabled = e.target.value !== 'آیپی مشترک آموزشی'; // only supported by {{Shared IP edu}} // localized
e.target.form.organization.disabled = false;
e.target.form.host.disabled = e.target.value === 'Whois'; // host= not supported by {{Whois}}
};
Twinkle.shared.callbacks = {
main: function(pageobj) {
var params = pageobj.getCallbackParameters();
var pageText = pageobj.getPageText();
var found = false;
var text = '{{';
for (var i = 0; i < Twinkle.shared.standardList.length; i++) {
var tagRe = new RegExp('(\\{\\{' + Twinkle.shared.standardList[i].value + '(\\||\\}\\}))', 'im');
if (tagRe.exec(pageText)) {
Morebits.status.warn('Info', 'الگوی {{' + Twinkle.shared.standardList[i].value + '}} روی صفحهٔ بحث پیدا شد؛ نصب الگو متوقف شد.'); // localized
found = true;
}
}
if (found) {
return;
}
Morebits.status.info('Info', 'الگوی نشانی آیپی مشترک را در بالای صفحه بحث کاربر اضافه میکند.'); // localized
text += params.value + '|' + params.organization;
if (params.value === 'Shared IP edu' && params.contact !== '') {
text += '|' + params.contact;
}
if (params.value !== 'Whois' && params.host !== '') {
text += '|host=' + params.host;
}
text += '}}\n\n';
var summaryText = 'Added {{[[Template:' + params.value + '|' + params.value + ']]}} template.';
pageobj.setPageText(text + pageText);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setMinorEdit(Twinkle.getPref('markSharedIPAsMinor'));
pageobj.setCreateOption('recreate');
pageobj.save();
}
};
Twinkle.shared.callback.evaluate = function friendlysharedCallbackEvaluate(e) {
var shared = e.target.getChecked('shared');
if (!shared || shared.length <= 0) {
alert('برای استفاده باید یک الگوی نشانی آیپی مشترک انتخاب کنید!'); // localized
return;
}
var value = shared[0];
if (e.target.organization.value === '') {
alert('برای الگوی {{' + value + '}} باید نام سازمان را وارد کنید!'); // localized
return;
}
var params = {
value: value,
organization: e.target.organization.value,
host: e.target.host.value,
contact: e.target.contact.value
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(e.target);
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'برچسب زدن کامل شد، بارگیری صفحه بحث ظرف چند ثانیهٔ دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'اصلاح صفحهٔ بحث کاربر'); // localized
wikipedia_page.setFollowRedirect(true);
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.shared.callbacks.main);
};
})(jQuery);
// </nowiki>
/* ===== friendlytalkback.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** friendlytalkback.js: Talkback module
****************************************
* Mode of invocation: Tab ("TB")
* Active on: Any page with relevant user name (userspace, contribs, etc.)
* Config directives in: FriendlyConfig
*/
Twinkle.talkback = function() {
if (!mw.config.get('wgRelevantUserName')) {
return;
}
Twinkle.addPortletLink(Twinkle.talkback.callback, 'بازبحث', 'friendly-talkback', 'گفتگوی آسان'); // localized
};
Twinkle.talkback.callback = function() {
if (mw.config.get('wgRelevantUserName') === mw.config.get('wgUserName') && !confirm("اوضاع این قدر بد است که میخواهید با خودتان گفتگو کنید؟")) { // localized
return;
}
var Window = new Morebits.simpleWindow(600, 350);
Window.setTitle('بازبحث'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('دربارهٔ {{بازبحث}}', 'الگو:بازبحث'); //localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#talkback'); //localized
var form = new Morebits.quickForm(Twinkle.talkback.evaluate);
form.append({ type: 'radio', name: 'tbtarget',
list: [
{
label: 'بازبحث: صفحهٔ بحث من', //localized
value: 'mytalk',
checked: 'true'
},
{
label: 'بازبحث: صفحهٔ بحث کاربر دیگر', //localized
value: 'usertalk'
},
{
label: 'بازبحث: صفحهای دیگر', //localized
value: 'other'
},
{
label: '«لطفاً ببینید»', //localized
value: 'see'
},
{
label: 'آگاهسازی درخصوص تابلوی اعلانات', //localized
value: 'notice'
},
{
label: '«رایانامه دارید»', //localized
value: 'mail'
}
],
event: Twinkle.talkback.changeTarget
});
form.append({
type: 'field',
label: 'Work area',
name: 'work_area'
});
var previewlink = document.createElement('a');
$(previewlink).click(function() {
Twinkle.talkback.preview(result); // |result| is defined below
});
previewlink.style.cursor = 'pointer';
previewlink.textContent = 'پیشنمایش'; // localized
form.append({ type: 'div', id: 'talkbackpreview', label: [ previewlink ] });
form.append({ type: 'div', id: 'friendlytalkback-previewbox', style: 'display: none' });
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
result.previewer = new Morebits.wiki.preview($(result).find('div#friendlytalkback-previewbox').last()[0]);
// We must init the
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.tbtarget[0].dispatchEvent(evt);
// Check whether the user has opted out from talkback
var query = {
action: 'query',
prop: 'extlinks',
titles: 'بحث کاربر:' + mw.config.get('wgRelevantUserName'), //localized
elquery: 'userjs.invalid/noTalkback',
ellimit: '1'
};
var wpapi = new Morebits.wiki.api('دریافت وضعیت استثنا از بازبحث', query, Twinkle.talkback.callback.optoutStatus); // localized
wpapi.post();
};
Twinkle.talkback.optout = '';
Twinkle.talkback.callback.optoutStatus = function(apiobj) {
var $el = $(apiobj.getXML()).find('el');
if ($el.length) {
Twinkle.talkback.optout = mw.config.get('wgRelevantUserName') + ' ترجیح میدهد بازبحث دریافت نکند'; // localized
var url = $el.text();
var reason = mw.util.getParamValue('reason', url);
Twinkle.talkback.optout += reason ? ': ' + reason : '.';
}
$('#twinkle-talkback-optout-message').text(Twinkle.talkback.optout);
};
var prev_page = '';
var prev_section = '';
var prev_message = '';
Twinkle.talkback.changeTarget = function(e) {
var value = e.target.values;
var root = e.target.form;
var old_area = Morebits.quickForm.getElements(root, 'work_area')[0];
if (root.section) {
prev_section = root.section.value;
}
if (root.message) {
prev_message = root.message.value;
}
if (root.page) {
prev_page = root.page.value;
}
var work_area = new Morebits.quickForm.element({
type: 'field',
label: 'اطلاعات بازبحث', // localized
name: 'work_area'
});
root.previewer.closePreview();
switch (value) {
case 'mytalk':
/* falls through */
default:
work_area.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-talkback-optout-message'
});
work_area.append({
type: 'input',
name: 'section',
label: 'عنوان بخش (اختیاری)', // localized
tooltip: 'عنوان بخشی از صفحهٔ بحث شما که در آن برای کاربر پیام گذاشتهاید. اگر چنین بخشی وجود ندارد این جعبه را خالی بگذارید.', // localized
value: prev_section
});
break;
case 'usertalk':
work_area.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-talkback-optout-message'
});
work_area.append({
type: 'input',
name: 'page',
label: 'کاربر (اجباری)', // localized
tooltip: 'نام کاربری که در صفحهٔ بحثش پیام گذاشتهاید. این پارامتر اجباری است.', // localized
value: prev_page,
required: true
});
work_area.append({
type: 'input',
name: 'section',
label: 'عنوان بخش (اختیاری)', // localized
tooltip: 'عنوان بخشی از صفحه که در آن پیام گذاشتهاید. اگر چنین بخشی وجود ندارد این پارامتر را خالی بگذارید.', // localized
value: prev_section
});
break;
case 'notice':
var noticeboard = work_area.append({
type: 'select',
name: 'noticeboard',
label: 'تابلوی اعلانات:', // localized
event: function(e) {
if (e.target.value === 'afchd') {
Morebits.quickForm.overrideElementLabel(e.target.form.section, 'عنوان پیشنویس (بدون پیشوند «پیشنویس»): '); // localized
Morebits.quickForm.setElementTooltipVisibility(e.target.form.section, false);
} else {
Morebits.quickForm.resetElementLabel(e.target.form.section);
Morebits.quickForm.setElementTooltipVisibility(e.target.form.section, true);
}
}
});
$.each(Twinkle.talkback.noticeboards, function(value, data) {
noticeboard.append({
type: 'option',
label: data.label,
value: value,
selected: !!data.defaultSelected
});
});
work_area.append({
type: 'input',
name: 'section',
label: 'ریسهٔ پیوندشده', // localized
tooltip: 'عنوان ریسهٔ مرتبط در تابلوی اعلانات.', // localized
value: prev_section
});
break;
case 'other':
work_area.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-talkback-optout-message'
});
work_area.append({
type: 'input',
name: 'page',
label: 'نام کامل صفحه (اجباری)', // localized
tooltip: "نام کامل صفحهای که در آن برای کاربر پیام گذاشتهاید. نام را با پیشوند وارد کنید، مثلاً «ویکیواژه:قهوهخانه/فنی». این پارامتر اجباری است.", // localized
value: prev_page,
required: true
});
work_area.append({
type: 'input',
name: 'section',
label: 'عنوان بخش (اختیاری)', // localized
tooltip: 'عنوان بخشی از صفحه که در آن پیام گذاشتهاید. اگر چنین بخشی وجود ندارد این پارامتر را خالی بگذارید.', // localized
value: prev_section
});
break;
case 'mail':
work_area.append({
type: 'input',
name: 'section',
label: 'موضوع رایانامه (اختیاری)', // localized
tooltip: 'عبارتی که به عنوان موضوع ایمیل ذکر کردید.' // localized
});
break;
case 'see':
work_area.append({
type: 'input',
name: 'page',
label: 'نام کامل صفحه (اجباری)', // localized
tooltip: "نام کامل صفحهای که در آن برای کاربر پیام گذاشتهاید. نام را با پیشوند وارد کنید، مثلاً «ویکیواژه:قهوهخانه/فنی». این پارامتر اجباری است.", // localized
value: prev_page,
required: true
});
work_area.append({
type: 'input',
name: 'section',
label: 'عنوان بخش (اختیاری)', // localized
tooltip: 'عنوان بخشی از صفحه که در آن پیام گذاشتهاید. اگر چنین بخشی وجود ندارد این پارامتر را خالی بگذارید.', // localized
value: prev_section
});
break;
}
if (value !== 'notice') {
work_area.append({ type: 'textarea', label: 'پیام اضافه (اختیاری):', name: 'message', tooltip: 'پیام اضافهای که میخواهید زیر الگوی بازبحث نوشته شود. امضای شما به طور خودکار به انتهای پیام اضافه خواهد شد.' }); // localized
}
work_area = work_area.render();
root.replaceChild(work_area, old_area);
if (root.message) {
root.message.value = prev_message;
}
$('#twinkle-talkback-optout-message').text(Twinkle.talkback.optout);
};
Twinkle.talkback.noticeboards = {
'an': {
label: "وو:تام (تابلوی اعلانات مدیران)", // localized
text: '== ' + Twinkle.getPref('adminNoticeHeading') + ' ==\n' +
"{{subst:آگاهسازی تام|بخش=$SECTION}} ~~~~", // localized
editSummary: 'آگاهسازی از بحثی که در [[ویکیواژه:تابلوی اعلانات مدیران]] مطرح شدهاست' // localized
},
'an3': {
label: "وو:ن۳ب (تابلوی اعلانات جنگ ویرایشی)", // localized
text: '{{subst:آگاهسازی تام۳|$SECTION}} ~~~~', // localized
editSummary: "آگاهسازی از شکایتی که در [[ویکیواژه:تابلوی اعلانات مدیران/نقض ۳ برگردان]] مطرح شدهاست." // localized
},
'ani': {
label: "شکایت در وو:تام (تابلوی اعلانات مدیران)", // localized
text: '== ' + Twinkle.getPref('adminNoticeHeading') + ' ==\n' +
"{{subst:آگاهسازی شکایت در تام|بخش=$SECTION}} ~~~~", // localized
editSummary: 'آگاهسازی از شکایتی که در [[ویکیواژه:تابلوی اعلانات مدیران]] مطرح شدهاست', // localized
defaultSelected: true
},
// let's keep AN and its cousins at the top
/*
'afchd': {
label: 'WP:AFCHD (Articles for creation/Help desk)',
text: '{{subst:AFCHD/u|$SECTION}} ~~~~',
editSummary: 'You have replies at the [[Wikipedia:AFCHD|Articles for Creation Help Desk]]'
},
'coin': {
label: 'WP:COIN (Conflict of interest noticeboard)',
text: '{{subst:Coin-notice|thread=$SECTION}} ~~~~',
editSummary: 'Notice of discussion at [[Wikipedia:Conflict of interest noticeboard]]'
},
*/ // localized [removed]
'drn': {
label: 'وو:تاحا (تابلوی اعلانات حل اختلاف)', // localized
text: '{{subst:آگاهسازی تاحا|بخش=$SECTION}} ~~~~', // localized
editSummary: 'آگاهسازی از بحثی در [[ویکیواژه:تابلوی اعلانات حل اختلاف]]' // localized
}/*,
'effp': {
label: 'WP:EFFP/R (Edit filter false positive report)',
text: '{{EFFPReply|1=$SECTION|2=~~~~}}',
editSummary: 'You have replies to your [[Wikipedia:Edit filter/False positives/Reports|edit filter false positive report]]'
},
'hd': {
label: 'WP:HD (Help desk)',
text: '== Your question at the Help desk ==\n' + '{{helpdeskreply|1=$SECTION|ts=~~~~~}}',
editSummary: 'You have replies at the [[Wikipedia:Help desk|Wikipedia help desk]]'
},
'th': {
label: 'WP:THQ (Teahouse question forum)',
text: "== Teahouse talkback: you've got messages! ==\n{{WP:Teahouse/Teahouse talkback|WP:Teahouse/Questions|$SECTION|ts=~~~~}}",
editSummary: 'You have replies at the [[Wikipedia:Teahouse/Questions|Teahouse question board]]'
},
'otrs': {
label: 'WP:OTRS/N (OTRS noticeboard)',
text: '{{OTRSreply|1=$SECTION|2=~~~~}}',
editSummary: 'You have replies at the [[Wikipedia:OTRS noticeboard|OTRS noticeboard]]'
}
*/ // localized [removed]
};
Twinkle.talkback.evaluate = function(e) {
var form = e.target;
var tbtarget = form.getChecked('tbtarget')[0];
var page, message;
var section = form.section.value;
var editSummary;
if (tbtarget === 'notice') {
page = form.noticeboard.value;
editSummary = Twinkle.talkback.noticeboards[page].editSummary;
} else {
// usertalk, other, see
page = form.page ? form.page.value : mw.config.get('wgUserName');
if (form.message) {
message = form.message.value.trim();
}
if (tbtarget === 'mail') {
editSummary = "اعلان: شما رایانامه دریافت کردید"; // localized
} else if (tbtarget === 'see') {
editSummary = 'لطفا بحث در [[:' + page + (section ? '#' + section : '') + ']] را ببینید'; // localized
} else { // tbtarget one of mytalk, usertalk, other
editSummary = 'بازبحث ([[:'; // localized
if (tbtarget !== 'other' && !/^\s*user talk:/i.test(page)) {
editSummary += 'بحث کاربر:'; // localized
}
editSummary += page + (section ? '#' + section : '') + ']])';
}
}
var text = '\n\n' + Twinkle.talkback.getNoticeWikitext(tbtarget, page, section, message);
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
var fullUserTalkPageName = mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').user_talk] + ':' + mw.config.get('wgRelevantUserName');
Morebits.wiki.actionCompleted.redirect = fullUserTalkPageName;
Morebits.wiki.actionCompleted.notice = 'بازبحث کامل شد؛ بارگیری مجدد صفحه بحث در چند ثانیه'; // localized
var talkpage = new Morebits.wiki.page(fullUserTalkPageName, 'اضافه کردن بازبحث'); // localized
talkpage.setAppendText(text);
talkpage.setEditSummary(editSummary + Twinkle.getPref('summaryAd'));
talkpage.setCreateOption('recreate');
talkpage.setMinorEdit(Twinkle.getPref('markTalkbackAsMinor'));
talkpage.setFollowRedirect(true);
talkpage.append();
};
Twinkle.talkback.preview = function(form) {
var tbtarget = form.getChecked('tbtarget')[0];
var section = form.section.value;
var page, message;
if (tbtarget === 'notice') {
page = form.noticeboard.value;
} else {
// usertalk, other, see
page = form.page ? form.page.value : mw.config.get('wgUserName');
if (form.message) {
message = form.message.value.trim();
}
}
var noticetext = Twinkle.talkback.getNoticeWikitext(tbtarget, page, section, message);
form.previewer.beginRender(noticetext, 'User_talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username
};
Twinkle.talkback.getNoticeWikitext = function(tbtarget, page, section, message) {
var text;
if (tbtarget === 'notice') {
text = Morebits.string.safeReplace(Twinkle.talkback.noticeboards[page].text, '$SECTION', section);
} else if (tbtarget === 'see') {
text = '{{جا:دعوت|مکان=' + page + (section ? '#' + section : '') + '|بیشتر=' + message.trim() + '}}'; // lacalized
} else {
text = '==';
if (tbtarget === 'mail') {
text += Twinkle.getPref('mailHeading') + '==\n' + "{{رایانامه|subject=" + section; // localized
} else { // tbtarget one of mytalk, usertalk, other
// clean talkback heading: strip section header markers that were erroneously suggested in the documentation
text += Twinkle.getPref('talkbackHeading').replace(/^\s*=+\s*(.*?)\s*=+$\s*/, '$1') +
'==\n' + '{{بازبحث|' + page + (section ? '|' + section : ''); // localized
}
text += '|ts=~~~~~}}';
if (message) {
text += '\n' + message + ' ~~~~';
} else if (Twinkle.getPref('insertTalkbackSignature')) {
text += '\n~~~~';
}
}
return text;
};
})(jQuery);
// </nowiki>
/* ===== twinklespeedy.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklespeedy.js: CSD module
****************************************
* Mode of invocation: Tab ("CSD")
* Active on: Non-special, existing pages
*
* NOTE FOR DEVELOPERS:
* If adding a new criterion, add it to the appropriate places at the top of
* twinkleconfig.js. Also check out the default values of the CSD preferences
* in twinkle.js, and add your new criterion to those if you think it would be
* good.
*/
Twinkle.speedy = function twinklespeedy() {
// Disable on:
// * special pages
// * non-existent pages
if (mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId')) {
return;
}
Twinkle.addPortletLink(Twinkle.speedy.callback, 'حذف سریع', 'tw-csd', Morebits.userIsSysop ? 'حذف صفحه طبق وو:محس' : 'درخواست حذف صفحه طبق وو:محس'); // localized
};
// This function is run when the CSD tab/header link is clicked
Twinkle.speedy.callback = function twinklespeedyCallback() {
Twinkle.speedy.initDialog(Morebits.userIsSysop ? Twinkle.speedy.callback.evaluateSysop : Twinkle.speedy.callback.evaluateUser, true);
};
// Used by unlink feature
Twinkle.speedy.dialog = null;
// Used throughout
Twinkle.speedy.hasCSD = !!$('#delete-reason').length;
// The speedy criteria list can be in one of several modes
Twinkle.speedy.mode = {
sysopSingleSubmit: 1, // radio buttons, no subgroups, submit when "Submit" button is clicked
sysopRadioClick: 2, // radio buttons, no subgroups, submit when a radio button is clicked
sysopMultipleSubmit: 3, // check boxes, subgroups, "Submit" button already present
sysopMultipleRadioClick: 4, // check boxes, subgroups, need to add a "Submit" button
userMultipleSubmit: 5, // check boxes, subgroups, "Submit" button already pressent
userMultipleRadioClick: 6, // check boxes, subgroups, need to add a "Submit" button
userSingleSubmit: 7, // radio buttons, subgroups, submit when "Submit" button is clicked
userSingleRadioClick: 8, // radio buttons, subgroups, submit when a radio button is clicked
// are we in "delete page" mode?
// (sysops can access both "delete page" [sysop] and "tag page only" [user] modes)
isSysop: function twinklespeedyModeIsSysop(mode) {
return mode === Twinkle.speedy.mode.sysopSingleSubmit ||
mode === Twinkle.speedy.mode.sysopMultipleSubmit ||
mode === Twinkle.speedy.mode.sysopRadioClick ||
mode === Twinkle.speedy.mode.sysopMultipleRadioClick;
},
// do we have a "Submit" button once the form is created?
hasSubmitButton: function twinklespeedyModeHasSubmitButton(mode) {
return mode === Twinkle.speedy.mode.sysopSingleSubmit ||
mode === Twinkle.speedy.mode.sysopMultipleSubmit ||
mode === Twinkle.speedy.mode.sysopMultipleRadioClick ||
mode === Twinkle.speedy.mode.userMultipleSubmit ||
mode === Twinkle.speedy.mode.userMultipleRadioClick ||
mode === Twinkle.speedy.mode.userSingleSubmit;
},
// is db-multiple the outcome here?
isMultiple: function twinklespeedyModeIsMultiple(mode) {
return mode === Twinkle.speedy.mode.userMultipleSubmit ||
mode === Twinkle.speedy.mode.sysopMultipleSubmit ||
mode === Twinkle.speedy.mode.userMultipleRadioClick ||
mode === Twinkle.speedy.mode.sysopMultipleRadioClick;
}
};
// Prepares the speedy deletion dialog and displays it
Twinkle.speedy.initDialog = function twinklespeedyInitDialog(callbackfunc) {
var dialog;
Twinkle.speedy.dialog = new Morebits.simpleWindow(Twinkle.getPref('speedyWindowWidth'), Twinkle.getPref('speedyWindowHeight'));
dialog = Twinkle.speedy.dialog;
dialog.setTitle('انتخاب معیار حذف سریع'); // localized
dialog.setScriptName('توینکل'); // localized
dialog.addFooterLink('سیاست حذف سریع', 'وو:محس'); // localized
dialog.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#speedy'); // localized
var form = new Morebits.quickForm(callbackfunc, Twinkle.getPref('speedySelectionStyle') === 'radioClick' ? 'change' : null);
if (Morebits.userIsSysop) {
form.append({
type: 'checkbox',
list: [
{
label: 'فقط برچسب بزن، حذف نکن', // localized
value: 'tag_only',
name: 'tag_only',
tooltip: 'اگر فقط قصد دارید به صفحه برچسب بزنید، نه این که آن را حذف سریع کنید، این گزینه را انتخاب کنید.', // localized
checked: !(Twinkle.speedy.hasCSD || Twinkle.getPref('deleteSysopDefaultToDelete')),
event: function(event) {
var cForm = event.target.form;
var cChecked = event.target.checked;
// enable talk page checkbox
if (cForm.talkpage) {
cForm.talkpage.checked = !cChecked && Twinkle.getPref('deleteTalkPageOnDelete');
}
// enable redirects checkbox
cForm.redirects.checked = !cChecked;
// enable delete multiple
cForm.delmultiple.checked = false;
// enable notify checkbox
cForm.notify.checked = cChecked;
// enable deletion notification checkbox
cForm.warnusertalk.checked = !cChecked && !Twinkle.speedy.hasCSD;
// enable multiple
cForm.multiple.checked = false;
// enable requesting creation protection
cForm.salting.checked = false;
Twinkle.speedy.callback.modeChanged(cForm);
event.stopPropagation();
}
}
]
});
var deleteOptions = form.append({
type: 'div',
name: 'delete_options'
});
deleteOptions.append({
type: 'header',
label: 'گزینههای حذف' // localized
});
if (mw.config.get('wgNamespaceNumber') % 2 === 0 && (mw.config.get('wgNamespaceNumber') !== 2 || (/\//).test(mw.config.get('wgTitle')))) { // hide option for user pages, to avoid accidentally deleting user talk page
deleteOptions.append({
type: 'checkbox',
list: [
{
label: 'صفحه بحث را نیز حذف کن', // localized
value: 'talkpage',
name: 'talkpage',
tooltip: "این گزینه صفحهٔ بحث را هم حذف میکند. اگر معیار پ۸ (پروندهٔ موجود در انبار) را انتخاب کنید، این گزینه نادیده گرفته میشود و صفحهٔ بحث حذف نمیشود.", // localized
checked: Twinkle.getPref('deleteTalkPageOnDelete'),
event: function(event) {
event.stopPropagation();
}
}
]
});
}
deleteOptions.append({
type: 'checkbox',
list: [
{
label: 'همچنین حذف تغییرمسیرها', // localized
value: 'redirects',
name: 'redirects',
tooltip: 'این گزینه تمام تغییرمسیرهای ورودی به صفحه را نیز حذف میکند. از این گزینه برای حذفهایی که جهت انتقال یا ادغام صفحه هستند استفاده نکنید.', // localized
checked: Twinkle.getPref('deleteRedirectsOnDelete'),
event: function(event) {
event.stopPropagation();
}
}
]
});
deleteOptions.append({
type: 'checkbox',
list: [
{
label: 'حذف بر اساس چند معیار', // localized
value: 'delmultiple',
name: 'delmultiple',
tooltip: 'اگر این گزینه را انتخاب کنید، میتوانید بیش از یک معیار را به عنوان دلیل حذف صفحه برگزینید. برای مثال، ترکیب معیار ع۱۱ و م۷ برای حذف مقالهها رایج است.', // localized
event: function(event) {
Twinkle.speedy.callback.modeChanged(event.target.form);
event.stopPropagation();
}
}
]
});
deleteOptions.append({
type: 'checkbox',
list: [
{
label: 'آگاه کردن سازندهٔ صفحه از حذف آن', // localized
value: 'warnusertalk',
name: 'warnusertalk',
tooltip: 'یک الگوی آگاهسازی روی صفحهٔ بحث کاربری که صفحه را ایجاد کرده قرار داده میشود، به شرط آن که در ترجیحات توینکل، آگاهسازی را برای این معیار فعال کرده باشید ' + // localized
'و این گزینه را هم تیک زده باشید. ممکن است برای سازندهٔ مقاله پیام خوشامدی هم فرستاده شود.', // localized
checked: !Twinkle.speedy.hasCSD,
event: function(event) {
event.stopPropagation();
}
}
]
});
}
var tagOptions = form.append({
type: 'div',
name: 'tag_options'
});
if (Morebits.userIsSysop) {
tagOptions.append({
type: 'header',
label: 'گزینههای مربوط به برچسبها' // localized
});
}
tagOptions.append({
type: 'checkbox',
list: [
{
label: 'آگاه کردن سازندهٔ صفحه در صورت امکان', // localized
value: 'notify',
name: 'notify',
tooltip: 'یک الگوی آگاهسازی روی صفحهٔ بحث کاربری که صفحه را ایجاد کرده قرار میگیرد، به شرط آن که در ترجیحات توینکل، آگاهسازی را برای این معیار فعال کرده باشید ' + // localized
'و این گزینه را هم تیک بزنید. ممکن است برای سازندهٔ صفحه پیام خوشامدی هم فرستاده شود.', // localized
checked: !Morebits.userIsSysop || !(Twinkle.speedy.hasCSD || Twinkle.getPref('deleteSysopDefaultToDelete')),
event: function(event) {
event.stopPropagation();
}
}
]
});
tagOptions.append({
type: 'checkbox',
list: [
{
label: 'برچسب برای محافظت در برابر ایجاد', // localized
value: 'salting',
name: 'salting',
tooltip: 'اگر این گزینه را انتخاب کنید، کنار برچسب حذف سریع یک برچسب {{نمک}} هم قرار میدهد تا از مدیران درخواست کند که صفحه پس از حذف، در برابر ایجاد هم محافظت شود. فقط در صورتی این گزینه را استفاده کنید که صفحه بارها ایجاد شده باشد.', // localized
event: function(event) {
event.stopPropagation();
}
}
]
});
tagOptions.append({
type: 'checkbox',
list: [
{
label: 'برچسب با چند دلیل', // localized
value: 'multiple',
name: 'multiple',
tooltip: 'اگر این گزینه را انتخاب کنید، میتوانید بیش از یک معیار را به عنوان دلیل حذف صفحه برگزینید. برای مثال، ترکیب معیار ع۱۱ و م۷ برای حذف مقالهها رایج است.', // localized
event: function(event) {
Twinkle.speedy.callback.modeChanged(event.target.form);
event.stopPropagation();
}
}
]
});
form.append({
type: 'div',
name: 'work_area',
label: 'فعال کردن پودمان حذف سریع ناموفق بود. لطفاً دوباره تلاش کنید یا در [[ویکیواژه:فنی]] گزارش کنید.' // localized
});
if (Twinkle.getPref('speedySelectionStyle') !== 'radioClick') {
form.append({ type: 'submit', className: 'tw-speedy-submit' }); // Renamed in modeChanged
}
var result = form.render();
dialog.setContent(result);
dialog.display();
Twinkle.speedy.callback.modeChanged(result);
};
Twinkle.speedy.callback.getMode = function twinklespeedyCallbackGetMode(form) {
var mode = Twinkle.speedy.mode.userSingleSubmit;
if (form.tag_only && !form.tag_only.checked) {
if (form.delmultiple.checked) {
mode = Twinkle.speedy.mode.sysopMultipleSubmit;
} else {
mode = Twinkle.speedy.mode.sysopSingleSubmit;
}
} else {
if (form.multiple.checked) {
mode = Twinkle.speedy.mode.userMultipleSubmit;
} else {
mode = Twinkle.speedy.mode.userSingleSubmit;
}
}
if (Twinkle.getPref('speedySelectionStyle') === 'radioClick') {
mode++;
}
return mode;
};
Twinkle.speedy.callback.modeChanged = function twinklespeedyCallbackModeChanged(form) {
var namespace = mw.config.get('wgNamespaceNumber');
// first figure out what mode we're in
var mode = Twinkle.speedy.callback.getMode(form);
var isSysopMode = Twinkle.speedy.mode.isSysop(mode);
if (isSysopMode) {
$('[name=delete_options]').show();
$('[name=tag_options]').hide();
$('button.tw-speedy-submit').text('حذف صفحه'); // localized
} else {
$('[name=delete_options]').hide();
$('[name=tag_options]').show();
$('button.tw-speedy-submit').text('برچسب زدن به صفحه'); // localized
}
var work_area = new Morebits.quickForm.element({
type: 'div',
name: 'work_area'
});
if (mode === Twinkle.speedy.mode.userMultipleRadioClick || mode === Twinkle.speedy.mode.sysopMultipleRadioClick) {
var evaluateType = isSysopMode ? 'evaluateSysop' : 'evaluateUser';
work_area.append({
type: 'div',
label: 'هنگامی که کارتان تمام شد، کلیک کنید:' // localized
});
work_area.append({
type: 'button',
name: 'submit-multiple',
label: isSysopMode ? 'حذف صفحه' : 'برچسب زدن به صفحه', // localized
event: function(event) {
Twinkle.speedy.callback[evaluateType](event);
event.stopPropagation();
}
});
}
var radioOrCheckbox = Twinkle.speedy.mode.isMultiple(mode) ? 'checkbox' : 'radio';
if (isSysopMode && !Twinkle.speedy.mode.isMultiple(mode)) {
work_area.append({ type: 'header', label: 'دلیل دیگر' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.customRationale, mode) });
}
if (namespace % 2 === 1 && namespace !== 3) {
// show db-talk on talk pages, but not user talk pages
work_area.append({ type: 'header', label: 'صفحههای بحث' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.talkList, mode) });
}
if (!mw.config.get('wgIsRedirect')) {
switch (namespace) {
case 0: // article
case 1: // talk
work_area.append({ type: 'header', label: 'مدخلها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.articleList, mode) });
break;
case 2: // user
case 3: // user talk
work_area.append({ type: 'header', label: 'صفحههای کاربری' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.userList, mode) });
break;
case 6: // file
case 7: // file talk
work_area.append({ type: 'header', label: 'پروندهها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.fileList, mode) });
if (!isSysopMode) {
work_area.append({ type: 'div', label: 'افزودن برچسب برای معیارهای پ۴ (بدون مجوز)، پ۵ (پروندهٔ غیر آزاد استفاده نشده)، پ۶ (فاقد دلیل برای استفادهٔ منصفانه) و پ۱۱ (بدون مدرک برای مجوز) با استفاده از زبانهٔ «تصاویر» در توینکل ممکن است.' }); // localized
}
break;
case 10: // template
case 11: // template talk
work_area.append({ type: 'header', label: 'الگوها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.templateList, mode) });
break;
case 14: // category
case 15: // category talk
work_area.append({ type: 'header', label: 'ردهها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.categoryList, mode) });
break;
case 100: // portal
case 101: // portal talk
work_area.append({ type: 'header', label: 'درگاهها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.portalList, mode) });
break;
default:
break;
}
} else {
if (namespace === 2 || namespace === 3) {
work_area.append({ type: 'header', label: 'صفحههای کاربری' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.userList, mode) });
}
work_area.append({ type: 'header', label: 'تغییرمسیرها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.redirectList, mode) });
}
var generalCriteria = Twinkle.speedy.generalList;
// custom rationale lives under general criteria when tagging
if (!isSysopMode) {
generalCriteria = Twinkle.speedy.customRationale.concat(generalCriteria);
}
work_area.append({ type: 'header', label: 'دلایل عمومی' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(generalCriteria, mode) });
var old_area = Morebits.quickForm.getElements(form, 'work_area')[0];
form.replaceChild(work_area.render(), old_area);
// Drop any section header that ended up with no criteria beneath it
// (some namespace criteria lists are empty on Wiktionary).
$(form).find('h5').each(function() {
var has = false, $n = $(this).next();
while ($n.length && !$n.is('h5')) {
if ($n.find('input').length) { has = true; break; }
$n = $n.next();
}
if (!has) { $(this).remove(); }
});
// if sysop, check if CSD is already on the page and fill in custom rationale
if (isSysopMode && Twinkle.speedy.hasCSD) {
var customOption = $('input[name=csd][value=reason]')[0];
if (customOption) {
if (Twinkle.getPref('speedySelectionStyle') !== 'radioClick') {
// force listeners to re-init
customOption.click();
customOption.parentNode.appendChild(customOption.subgroup);
}
customOption.subgroup.querySelector('input').value = decodeURIComponent($('#delete-reason').text()).replace(/\+/g, ' ');
}
}
};
Twinkle.speedy.generateCsdList = function twinklespeedyGenerateCsdList(list, mode) {
// mode switches
var isSysopMode = Twinkle.speedy.mode.isSysop(mode);
var multiple = Twinkle.speedy.mode.isMultiple(mode);
var hasSubmitButton = Twinkle.speedy.mode.hasSubmitButton(mode);
var pageNamespace = mw.config.get('wgNamespaceNumber');
var openSubgroupHandler = function(e) {
$(e.target.form).find('input').prop('disabled', true);
$(e.target.form).children().css('color', 'gray');
$(e.target).parent().css('color', 'black').find('input').prop('disabled', false);
$(e.target).parent().find('input:text')[0].focus();
e.stopPropagation();
};
var submitSubgroupHandler = function(e) {
var evaluateType = Twinkle.speedy.mode.isSysop(mode) ? 'evaluateSysop' : 'evaluateUser';
Twinkle.speedy.callback[evaluateType](e);
e.stopPropagation();
};
return $.map(list, function(critElement) {
var criterion = $.extend({}, critElement);
if (multiple) {
if (criterion.hideWhenMultiple) {
return null;
}
if (criterion.hideSubgroupWhenMultiple) {
criterion.subgroup = null;
}
} else {
if (criterion.hideWhenSingle) {
return null;
}
if (criterion.hideSubgroupWhenSingle) {
criterion.subgroup = null;
}
}
if (isSysopMode) {
if (criterion.hideWhenSysop) {
return null;
}
if (criterion.hideSubgroupWhenSysop) {
criterion.subgroup = null;
}
} else {
if (criterion.hideWhenUser) {
return null;
}
if (criterion.hideSubgroupWhenUser) {
criterion.subgroup = null;
}
}
if (mw.config.get('wgIsRedirect') && criterion.hideWhenRedirect) {
return null;
}
if (criterion.showInNamespaces && criterion.showInNamespaces.indexOf(pageNamespace) < 0) {
return null;
}
if (criterion.hideInNamespaces && criterion.hideInNamespaces.indexOf(pageNamespace) > -1) {
return null;
}
if (criterion.subgroup && !hasSubmitButton) {
if (Array.isArray(criterion.subgroup)) {
criterion.subgroup = criterion.subgroup.concat({
type: 'button',
name: 'submit',
label: isSysopMode ? 'حذف صفحه' : 'برچسب زدن به صفحه', // localized
event: submitSubgroupHandler
});
} else {
criterion.subgroup = [
criterion.subgroup,
{
type: 'button',
name: 'submit', // ends up being called "csd.submit" so this is OK
label: isSysopMode ? 'حذف صفحه' : 'برچسب زدن به صفحه', // localized
event: submitSubgroupHandler
}
];
}
// FIXME: does this do anything?
criterion.event = openSubgroupHandler;
}
return criterion;
});
};
Twinkle.speedy.customRationale = [
{
label: 'دلیل دیگر' + (Morebits.userIsSysop ? ' (دلیل شخصیسازیشده برای حذف)' : ' با استفاده از الگوی {{حذف سریع}}'), // localized
value: 'reason',
tooltip: 'دست کم یکی از معیارهای حذف سریع باید برای صفحه مصداق داشته باشد، و شما این را باید در دلیلی که مینویسید ذکر کنید. این گزینه برای آن نیست که وقتی دلیل حذف را بین گزینههای دیگر نیافتید از آن استفاده کنید.', // localized
subgroup: {
name: 'reason_1',
type: 'input',
label: 'دلیل: ', // localized
size: 60
},
hideWhenMultiple: true
}
];
Twinkle.speedy.talkList = [
{
label: 'بحث یتیم: صفحهٔ بحثی بدون صفحهٔ اصلی',
value: 'talk',
reason: 'بحث یتیم',
tooltip: 'صفحهٔ بحثی که صفحهٔ اصلی متناظر آن وجود ندارد یا حذف شدهاست. شامل بایگانیهای بحث و صفحههای بحث پروندههای موجود در ویکیانبار نمیشود.'
}
];
Twinkle.speedy.fileList = [
{
label: 'تکراری: نسخهٔ یکسان در ویکیواژه یا ویکیانبار موجود است',
value: 'duplicate',
reason: 'تکراری',
tooltip: 'پروندهای که نسخهٔ کاملاً یکسان آن هماکنون در ویکیواژه یا ویکیانبار موجود است.'
},
{
label: 'استفاده نشده: پروندهٔ بیکاربرد',
value: 'unused',
reason: 'استفاده نشده',
tooltip: 'پروندهای که در هیچ صفحهای به کار نرفته و کاربردی برای آن متصور نیست.'
}
];
Twinkle.speedy.articleList = [];
Twinkle.speedy.categoryList = [
{
label: 'استفاده نشده: ردهٔ خالی و بیکاربرد',
value: 'unused',
reason: 'استفاده نشده',
tooltip: 'ردهای که خالی است و کاربردی برای آن متصور نیست.'
}
];
Twinkle.speedy.userList = [];
Twinkle.speedy.templateList = [
{
label: 'استفاده نشده: الگوی بیکاربرد',
value: 'unused',
reason: 'استفاده نشده',
tooltip: 'الگویی که در هیچ صفحهای به کار نرفته و کاربردی برای آن متصور نیست.'
}
];
Twinkle.speedy.portalList = [];
Twinkle.speedy.generalList = [
{
label: 'خرابکاری یا آزمایش محض',
value: 'vandalism',
reason: 'خرابکاری',
tooltip: 'صفحهای که تنها برای خرابکاری، آزمایش یا محتوای بیمعنا ساخته شدهاست.'
},
{
label: 'حذفشده: بازسازی صفحهٔ پیشتر حذفشده',
value: 'deleted',
reason: 'حذف شده',
tooltip: 'صفحهای که پیشتر حذف شده و بی هیچ تغییر معناداری دوباره ساخته شدهاست.'
},
{
label: 'نقض حق تکثیر',
value: 'copyvio',
reason: 'نقض حق تکثیر',
tooltip: 'صفحهای که آشکارا رونوشت از منبعی دارای حق تکثیر است و محتوای آزاد قابلنگهداری ندارد.',
subgroup: {
name: 'copyvio_url',
type: 'input',
label: 'نشانی منبع (اختیاری): ',
size: 60
}
},
{
label: 'تکراری: نسخهٔ یکسان موجود است',
value: 'duplicate',
reason: 'تکراری',
tooltip: 'مدخل یا صفحهای که نسخهٔ یکسان آن هماکنون در ویکیواژه موجود است.',
hideInNamespaces: [ 6, 14, 10 ]
},
{
label: 'منتقلشده به پروژهٔ دیگر ویکیمدیا',
value: 'moved',
reason: 'ویکیمدیا',
tooltip: 'محتوایی که به پروژهٔ دیگری از بنیاد ویکیمدیا منتقل شده و ماندن آن در ویکیواژه لازم نیست.'
}
];
Twinkle.speedy.redirectList = [];
Twinkle.speedy.normalizeHash = {
'reason': 'حذف سریع',
'vandalism': 'خرابکاری',
'deleted': 'حذف شده',
'copyvio': 'نقض حق تکثیر',
'duplicate': 'تکراری',
'unused': 'استفاده نشده',
'moved': 'ویکیمدیا',
'talk': 'بحث یتیم'
};
Twinkle.speedy.callbacks = {
getTemplateCodeAndParams: function(params) {
var reasonFor = function(value, tp) {
if (value === 'reason') {
return (tp && tp['1']) ? tp['1'] : '';
}
var r = Twinkle.speedy.normalizeHash[value] || value;
if (value === 'copyvio' && tp && tp.url) {
r += ' ([' + tp.url + '])';
}
return r;
};
var reasons = [];
$.each(params.values, function(index, value) {
var r = reasonFor(value, params.templateParams[index]);
if (r) {
reasons.push(r);
}
});
if (!reasons.length) {
reasons.push('حذف سریع');
}
var code;
if (reasons.length > 2) {
code = '{{حذف سریع|' + reasons[0] + '|' + reasons.slice(1).join('؛ ') + '}}';
} else {
code = '{{حذف سریع|' + reasons.join('|') + '}}';
}
params.utparams = { reason: reasons.join('؛ ') };
return [code, params.utparams];
},
parseWikitext: function(wikitext, callback) {
var query = {
action: 'parse',
prop: 'text',
pst: 'true',
text: wikitext,
contentmodel: 'wikitext',
title: mw.config.get('wgPageName')
};
var statusIndicator = new Morebits.status('ایجاد خلاصهٔ حذف'); // localized
var api = new Morebits.wiki.api('Parsing deletion template', query, function(apiObj) {
var reason = decodeURIComponent($(apiObj.getXML().querySelector('text').childNodes[0].nodeValue).find('#delete-reason').text()).replace(/\+/g, ' ');
if (!reason) {
statusIndicator.warn('امکان ایجاد خلاصه از الگوی حذف وجود ندارد'); // localized
} else {
statusIndicator.info('کامل'); // localized
}
callback(reason);
}, statusIndicator);
api.post();
},
noteToCreator: function(pageobj) {
var params = pageobj.getCallbackParameters();
var initialContrib = pageobj.getCreator();
// disallow notifying yourself
if (initialContrib === mw.config.get('wgUserName')) {
Morebits.status.warn('شما (' + initialContrib + ') این صفحه را ایجاد کردهاید؛ توقف آگاهسازی کاربر'); // localized
initialContrib = null;
// don't notify users when their user talk page is nominated/deleted
} else if (initialContrib === mw.config.get('wgTitle') && mw.config.get('wgNamespaceNumber') === 3) {
Morebits.status.warn('Notifying initial contributor: this user created their own user talk page; skipping notification');
initialContrib = null;
// quick hack to prevent excessive unwanted notifications, per request. Should actually be configurable on recipient page...
} else if ((initialContrib === 'Cyberbot I' || initialContrib === 'SoxBot') && params.normalizeds[0] === 'f2') {
Morebits.status.warn('Notifying initial contributor: page created procedurally by bot; skipping notification');
initialContrib = null;
// Check for already existing tags
} else if (Twinkle.speedy.hasCSD && params.warnUser && !confirm('The page is has a deletion-related tag, and thus the creator has likely been notified. Do you want to notify them for this deletion as well?')) {
Morebits.status.info('Notifying initial contributor', 'canceled by user; skipping notification.');
initialContrib = null;
}
if (initialContrib) {
var usertalkpage = new Morebits.wiki.page('User talk:' + initialContrib, 'Notifying initial contributor (' + initialContrib + ')'),
notifytext, i, editsummary;
notifytext = '\n{{جا:هشدار حذف سریع|' + Morebits.pageNameNorm + '|' + ((params.utparams && params.utparams.reason) || 'حذف سریع') + '}} ~~~~';
editsummary = 'آگاهسازی: ' + (params.warnUser ? '' : 'نامزدی ') + 'حذف سریع'; // localized
if (params.normalizeds.indexOf('g10') === -1) { // no article name in summary for G10 taggings
editsummary += ' [[:' + Morebits.pageNameNorm + ']].';
} else {
editsummary += ' صفحهٔ حمله.'; // localized
}
usertalkpage.setAppendText(notifytext);
usertalkpage.setEditSummary(editsummary + Twinkle.getPref('summaryAd'));
usertalkpage.setCreateOption('recreate');
usertalkpage.setFollowRedirect(true);
usertalkpage.append(function onNotifySuccess() {
// add this nomination to the user's userspace log, if the user has enabled it
if (params.lognomination) {
Twinkle.speedy.callbacks.user.addToLog(params, initialContrib);
}
}, function onNotifyError() {
// if user could not be notified, log nomination without mentioning that notification was sent
if (params.lognomination) {
Twinkle.speedy.callbacks.user.addToLog(params, null);
}
});
} else if (params.lognomination) {
// log nomination even if the user notification wasn't sent
Twinkle.speedy.callbacks.user.addToLog(params, null);
}
},
sysop: {
main: function(params) {
var reason;
if (!params.normalizeds.length && params.normalizeds[0] === 'db') {
reason = prompt('Enter the deletion summary to use, which will be entered into the deletion log:', '');
Twinkle.speedy.callbacks.sysop.deletePage(reason, params);
} else {
var code = Twinkle.speedy.callbacks.getTemplateCodeAndParams(params)[0];
Twinkle.speedy.callbacks.parseWikitext(code, function(reason) {
if (params.promptForSummary) {
reason = prompt('Enter the deletion summary to use, or press OK to accept the automatically generated one.', reason);
}
Twinkle.speedy.callbacks.sysop.deletePage(reason, params);
});
}
},
deletePage: function(reason, params) {
var thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'حذف صفحه'); // localized
if (reason === null) {
return Morebits.status.error('درخواست دلیل', 'لغو توسط کاربر'); // localized
} else if (!reason || !reason.replace(/^\s*/, '').replace(/\s*$/, '')) {
return Morebits.status.error('درخواست دلیل', "you didn't give one. I don't know... what with admins and their apathetic antics... I give up...");
}
var deleteMain = function(callback) {
thispage.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
thispage.deletePage(function() {
thispage.getStatusElement().info('انجام شد'); // localized
typeof callback === 'function' && callback();
Twinkle.speedy.callbacks.sysop.deleteTalk(params);
});
};
// look up initial contributor. If prompting user for deletion reason, just display a link.
// Otherwise open the talk page directly
if (params.warnUser) {
thispage.setCallbackParameters(params);
thispage.lookupCreation(function(pageobj) {
deleteMain(function() {
Twinkle.speedy.callbacks.noteToCreator(pageobj);
});
});
} else {
deleteMain();
}
},
deleteTalk: function(params) {
// delete talk page
if (params.deleteTalkPage &&
params.normalized !== 'f8' &&
document.getElementById('ca-talk').className !== 'new') {
var talkpage = new Morebits.wiki.page(mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceNumber') + 1] + ':' + mw.config.get('wgTitle'), 'حذف صفحهٔ بحث'); // localized
talkpage.setEditSummary('[[WP:CSD#G8|G8]]: Talk page of deleted page "' + Morebits.pageNameNorm + '"' + Twinkle.getPref('deletionSummaryAd'));
talkpage.deletePage();
// this is ugly, but because of the architecture of wiki.api, it is needed
// (otherwise success/failure messages for the previous action would be suppressed)
window.setTimeout(function() {
Twinkle.speedy.callbacks.sysop.deleteRedirects(params);
}, 1800);
} else {
Twinkle.speedy.callbacks.sysop.deleteRedirects(params);
}
},
deleteRedirects: function(params) {
// delete redirects
if (params.deleteRedirects) {
var query = {
'action': 'query',
'titles': mw.config.get('wgPageName'),
'prop': 'redirects',
'rdlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
var wikipedia_api = new Morebits.wiki.api('getting list of redirects...', query, Twinkle.speedy.callbacks.sysop.deleteRedirectsMain,
new Morebits.status('در حال حذف تغییرمسیرها')); // localized
wikipedia_api.params = params;
wikipedia_api.post();
}
// promote Unlink tool
var $link, $bigtext;
if (mw.config.get('wgNamespaceNumber') === 6 && params.normalized !== 'f8') {
$link = $('<a/>', {
'href': '#',
'text': 'click here to go to the Unlink tool',
'css': { 'fontSize': '130%', 'fontWeight': 'bold' },
'click': function() {
Morebits.wiki.actionCompleted.redirect = null;
Twinkle.speedy.dialog.close();
Twinkle.unlink.callback('Removing usages of and/or links to deleted file ' + Morebits.pageNameNorm);
}
});
$bigtext = $('<span/>', {
'text': 'To orphan backlinks and remove instances of file usage',
'css': { 'fontSize': '130%', 'fontWeight': 'bold' }
});
Morebits.status.info($bigtext[0], $link[0]);
} else if (params.normalized !== 'f8') {
$link = $('<a/>', {
'href': '#',
'text': 'click here to go to the Unlink tool',
'css': { 'fontSize': '130%', 'fontWeight': 'bold' },
'click': function() {
Morebits.wiki.actionCompleted.redirect = null;
Twinkle.speedy.dialog.close();
Twinkle.unlink.callback('Removing links to deleted page ' + Morebits.pageNameNorm);
}
});
$bigtext = $('<span/>', {
'text': 'To orphan backlinks',
'css': { 'fontSize': '130%', 'fontWeight': 'bold' }
});
Morebits.status.info($bigtext[0], $link[0]);
}
},
deleteRedirectsMain: function(apiobj) {
var xmlDoc = apiobj.getXML();
var $snapshot = $(xmlDoc).find('redirects rd');
var total = $snapshot.length;
var statusIndicator = apiobj.statelem;
if (!total) {
statusIndicator.status('هیچ تغییرمسیری پیدا نشد'); // localized
return;
}
statusIndicator.status('0%');
var current = 0;
var onsuccess = function(apiobjInner) {
var now = parseInt(100 * ++current / total, 10) + '%';
statusIndicator.update(now);
apiobjInner.statelem.unlink();
if (current >= total) {
statusIndicator.info(now + ' (کامل شد)'); // localized
Morebits.wiki.removeCheckpoint();
}
};
Morebits.wiki.addCheckpoint();
$snapshot.each(function(key, value) {
var title = $(value).attr('title');
var page = new Morebits.wiki.page(title, 'در حال حذف تغییرمسیر "' + title + '"'); // localized
page.setEditSummary('[[WP:CSD#G8|G8]]: Redirect to deleted page "' + Morebits.pageNameNorm + '"' + Twinkle.getPref('deletionSummaryAd'));
page.deletePage(onsuccess);
});
}
},
user: {
main: function(pageobj) {
var statelem = pageobj.getStatusElement();
// defaults to /doc for lua modules, which may not exist
if (!pageobj.exists() && mw.config.get('wgPageContentModel') !== 'Scribunto') {
statelem.error("It seems that the page doesn't exist; perhaps it has already been deleted");
return;
}
var text = pageobj.getPageText();
var params = pageobj.getCallbackParameters();
statelem.status('در حال بررسی برچسبهای روی صفحه...'); // localized
// check for existing deletion tags
var tag = /(?:\{\{\s*(db|delete|db-.*?|speedy deletion-.*?)(?:\s*\||\s*\}\}))/.exec(text);
// This won't make use of the db-multiple template but it probably should
if (tag && !confirm('The page already has the CSD-related template {{' + tag[1] + '}} on it. Do you want to add another CSD template?')) {
return;
}
var xfd = /\{\{((?:article for deletion|proposed deletion|prod blp|template for discussion)\/dated|[cfm]fd\b)/i.exec(text) || /#invoke:(RfD)/.exec(text);
if (xfd && !confirm('The deletion-related template {{' + xfd[1] + '}} was found on the page. Do you still want to add a CSD template?')) {
return;
}
// given the params, builds the template and also adds the user talk page parameters to the params that were passed in
// returns => [<string> wikitext, <object> utparams]
var buildData = Twinkle.speedy.callbacks.getTemplateCodeAndParams(params),
code = buildData[0];
params.utparams = buildData[1];
// curate/patrol the page
if (Twinkle.getPref('markSpeedyPagesAsPatrolled')) {
pageobj.triage();
}
// Wrap SD template in noinclude tags if we are in template space.
// Won't work with userboxes in userspace, or any other transcluded page outside template space
if (mw.config.get('wgNamespaceNumber') === 10) { // Template:
code = '<noinclude>' + code + '</noinclude>';
}
// Remove tags that become superfluous with this action
text = text.replace(/\{\{\s*([Uu]serspace draft)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/g, '');
if (mw.config.get('wgNamespaceNumber') === 6) {
// remove "move to Commons" tag - deletion-tagged files cannot be moved to Commons
text = text.replace(/\{\{(mtc|(copy |move )?to ?commons|move to wikimedia commons|copy to wikimedia commons)[^}]*\}\}/gi, '');
}
if (params.requestsalt) {
if (params.normalizeds.indexOf('g10') === -1) {
code = code + '\n{{نمک}}'; // localized
} else {
code = '{{نمک}}\n' + code; // localized
}
}
// Generate edit summary for edit
var editsummary;
if (params.normalizeds.length > 1) {
editsummary = 'ثبت درخواست حذف سریع ('; // localized
$.each(params.normalizeds, function(index, norm) {
editsummary += '[[ویکیواژه:محس#' + norm.toUpperCase() + '|محس ' + norm.toUpperCase() + ']], '; // localized
});
editsummary = editsummary.substr(0, editsummary.length - 2); // remove trailing comma
editsummary += ').';
} else if (params.normalizeds[0] === 'db') {
editsummary = 'ثبت درخواست [[ویکیواژه:محس|حذف سریع]] با معیار «' + params.templateParams[0]['1'] + '».'; // localized
} else {
editsummary = 'ثبت درخواست حذف سریع ([[ویکیواژه:محس#' + params.normalizeds[0].toUpperCase() + '|محس ' + params.normalizeds[0].toUpperCase() + ']]).'; // localized
}
// Set the correct value for |ts= parameter in {{db-g13}}
if (params.normalizeds.indexOf('g13') !== -1) {
code = code.replace('$TIMESTAMP', pageobj.getLastEditTime());
}
pageobj.setPageText(code + (params.normalizeds.indexOf('g10') !== -1 ? '' : '\n' + text)); // cause attack pages to be blanked
pageobj.setEditSummary(editsummary + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(params.watch);
if (params.scribunto) {
pageobj.setCreateOption('recreate'); // Module /doc might not exist
if (params.watch) {
// Watch module in addition to /doc subpage
var watch_query = {
action: 'watch',
titles: mw.config.get('wgPageName'),
token: mw.user.tokens.get('watchToken')
};
new Morebits.wiki.api('Adding Module to watchlist', watch_query).post();
}
}
pageobj.save(Twinkle.speedy.callbacks.user.tagComplete);
},
tagComplete: function(pageobj) {
var params = pageobj.getCallbackParameters();
// Notification to first contributor, will also log nomination to the user's userspace log
if (params.usertalk) {
var thispage = new Morebits.wiki.page(Morebits.pageNameNorm);
thispage.setCallbackParameters(params);
thispage.lookupCreation(Twinkle.speedy.callbacks.noteToCreator);
// or, if not notifying, add this nomination to the user's userspace log without the initial contributor's name
} else if (params.lognomination) {
Twinkle.speedy.callbacks.user.addToLog(params, null);
}
},
// note: this code is also invoked from twinkleimage
// the params used are:
// for CSD: params.values, params.normalizeds (note: normalizeds is an array)
// for DI: params.fromDI = true, params.templatename, params.normalized (note: normalized is a string)
addToLog: function(params, initialContrib) {
var usl = new Morebits.userspaceLogger(Twinkle.getPref('speedyLogPageName'));
usl.initialText =
"This is a log of all [[WP:CSD|speedy deletion]] nominations made by this user using [[WP:TW|Twinkle]]'s CSD module.\n\n" +
'If you no longer wish to keep this log, you can turn it off using the [[Wikipedia:Twinkle/Preferences|preferences panel]], and ' +
'nominate this page for speedy deletion under [[WP:CSD#U1|CSD U1]].' +
(Morebits.userIsSysop ? '\n\nThis log does not track outright speedy deletions made using Twinkle.' : '');
var formatParamLog = function(normalize, csdparam, input) {
if ((normalize === 'G4' && csdparam === 'xfd') ||
(normalize === 'G6' && csdparam === 'page') ||
(normalize === 'G6' && csdparam === 'fullvotepage') ||
(normalize === 'G6' && csdparam === 'sourcepage') ||
(normalize === 'A2' && csdparam === 'source') ||
(normalize === 'A10' && csdparam === 'article') ||
(normalize === 'F1' && csdparam === 'filename') ||
(normalize === 'F5' && csdparam === 'replacement')) {
input = '[[:' + input + ']]';
} else if (normalize === 'G5' && csdparam === 'user') {
input = '[[:User:' + input + ']]';
} else if (normalize === 'G12' && csdparam.lastIndexOf('url', 0) === 0 && input.lastIndexOf('http', 0) === 0) {
input = '[' + input + ' ' + input + ']';
} else if (normalize === 'T3' && csdparam === 'template') {
input = '[[:Template:' + input + ']]';
} else if (normalize === 'F8' && csdparam === 'filename') {
input = '[[commons:' + input + ']]';
} else if (normalize === 'P1' && csdparam === 'criterion') {
input = '[[WP:CSD#' + input + ']]';
}
return ' {' + normalize + ' ' + csdparam + ': ' + input + '}';
};
var extraInfo = '';
// If a logged file is deleted but exists on commons, the wikilink will be blue, so provide a link to the log
var fileLogLink = mw.config.get('wgNamespaceNumber') === 6 ? ' ([{{fullurl:Special:Log|page=' + mw.util.wikiUrlencode(mw.config.get('wgPageName')) + '}} log])' : '';
var editsummary = 'Logging speedy deletion nomination';
var appendText = '# [[:' + Morebits.pageNameNorm;
if (params.fromDI) {
appendText += ']]' + fileLogLink + ': DI [[WP:CSD#' + params.normalized.toUpperCase() + '|CSD ' + params.normalized.toUpperCase() + ']] ({{tl|di-' + params.templatename + '}})';
// The params data structure when coming from DI is quite different,
// so this hardcodes the only interesting items worth logging
['reason', 'replacement', 'source'].forEach(function(item) {
if (params[item]) {
extraInfo += formatParamLog(params.normalized.toUpperCase(), item, params[item]);
return false;
}
});
editsummary += ' [[:' + Morebits.pageNameNorm + ']].'; // localized
} else {
if (params.normalizeds.indexOf('g10') === -1) { // no article name in log for G10 taggings
appendText += ']]' + fileLogLink + ': ';
editsummary += ' [[:' + Morebits.pageNameNorm + ']].'; // localized
} else {
appendText += '|این]] صفحهٔ حمله' + fileLogLink + ': '; // localized
editsummary += ' یک صفحهٔ حمله.'; // localized
}
if (params.normalizeds.length > 1) {
appendText += 'چند معیار ('; // localized
$.each(params.normalizeds, function(index, norm) {
appendText += '[[WP:CSD#' + norm.toUpperCase() + '|' + norm.toUpperCase() + ']], ';
});
appendText = appendText.substr(0, appendText.length - 2); // remove trailing comma
appendText += ')';
} else if (params.normalizeds[0] === 'db') {
appendText += '{{tl|db-reason}}';
} else {
appendText += '[[WP:CSD#' + params.normalizeds[0].toUpperCase() + '|CSD ' + params.normalizeds[0].toUpperCase() + ']] ({{tl|db-' + params.values[0] + '}})';
}
// If params is "empty" it will still be full of empty arrays, but ask anyway
if (params.templateParams) {
// Treat custom rationale individually
if (params.normalizeds[0] && params.normalizeds[0] === 'db') {
extraInfo += formatParamLog('Custom', 'rationale', params.templateParams[0]['1']);
} else {
params.templateParams.forEach(function(item, index) {
var keys = Object.keys(item);
if (keys[0] !== undefined && keys[0].length > 0) {
// Second loop required since some items (G12, F9) may have multiple keys
keys.forEach(function(key, keyIndex) {
if (keys[keyIndex] === 'blanked' || keys[keyIndex] === 'ts') {
return true; // Not worth logging
}
extraInfo += formatParamLog(params.normalizeds[index].toUpperCase(), keys[keyIndex], item[key]);
});
}
});
}
}
}
if (params.requestsalt) {
appendText += '; حفاظت در برابر ایجاد درخواست شدهاست ([[ویکیواژه:نمک|نمکخورده]])'; // localized
}
if (extraInfo) {
appendText += '؛ اطلاعات اضافی:' + extraInfo; // localized
}
if (initialContrib) {
appendText += '؛ {{user|1=' + initialContrib + '}} آگاه شد'; // localized
}
appendText += ' ~~~~~\n';
usl.log(appendText, editsummary + Twinkle.getPref('summaryAd'));
}
}
};
// validate subgroups in the form passed into the speedy deletion tag
Twinkle.speedy.getParameters = function twinklespeedyGetParameters(form, values) {
var parameters = [];
$.each(values, function(index, value) {
var currentParams = [];
switch (value) {
case 'reason':
if (form['csd.reason_1']) {
var dbrationale = form['csd.reason_1'].value;
if (!dbrationale || !dbrationale.trim()) {
alert('دلیل دلخواه: لطفاً دلیلی ارائه کنید.'); // localized
parameters = null;
return false;
}
currentParams['1'] = dbrationale;
}
break;
case 'userreq': // U1
if (form['csd.userreq_rationale']) {
var u1rationale = form['csd.userreq_rationale'].value;
if (mw.config.get('wgNamespaceNumber') === 3 && !(/\//).test(mw.config.get('wgTitle')) &&
(!u1rationale || !u1rationale.trim())) {
alert('CSD U1: Please specify a rationale when nominating user talk pages.');
parameters = null;
return false;
}
currentParams.rationale = u1rationale;
}
break;
case 'repost': // G4
if (form['csd.repost_xfd']) {
var deldisc = form['csd.repost_xfd'].value;
if (deldisc) {
if (!/^(?:وپ|ویکیپدیا):/i.test(deldisc)) { // localized
alert('محس ع۴: صفحه نظرخواهی حذف باید با "ویکیواژه:" آغاز شود.'); // localized
parameters = null;
return false;
}
currentParams.xfd = deldisc;
}
}
break;
case 'banned': // G5
if (form['csd.banned_user'] && form['csd.banned_user'].value) {
currentParams.user = form['csd.banned_user'].value.replace(/^\s*کاربر:/i, ''); // localized
}
break;
case 'move': // G6
if (form['csd.move_page'] && form['csd.move_reason']) {
var movepage = form['csd.move_page'].value,
movereason = form['csd.move_reason'].value;
if (!movepage || !movepage.trim()) {
alert('محس ع۶ (انتقال): لطفاً نام صفحهای که باید به اینجا انتقال یابد مشخص کنید.'); // localized
parameters = null;
return false;
}
if (!movereason || !movereason.trim()) {
alert('محس ع۶ (انتقال): لطفاً دلیل انتقال را مشخص کنید.'); // localized
parameters = null;
return false;
}
currentParams.page = movepage;
currentParams.reason = movereason;
}
break;
case 'xfd': // G6
if (form['csd.xfd_fullvotepage']) {
var xfd = form['csd.xfd_fullvotepage'].value;
if (xfd) {
if (!/^(?:wp|wikipedia):/i.test(xfd)) {
alert('CSD G6 (XFD): The deletion discussion page name, if provided, must start with "Wikipedia:".');
parameters = null;
return false;
}
currentParams.fullvotepage = xfd;
}
}
break;
case 'copypaste': // G6
if (form['csd.copypaste_sourcepage']) {
var copypaste = form['csd.copypaste_sourcepage'].value;
if (!copypaste || !copypaste.trim()) {
alert('محس ع۶ (کپیپیست): لطفاً نام صفحهٔ مبدأ را مشخص کنید.'); // localized
parameters = null;
return false;
}
currentParams.sourcepage = copypaste;
}
break;
case 'g6': // G6
if (form['csd.g6_rationale'] && form['csd.g6_rationale'].value) {
currentParams.rationale = form['csd.g6_rationale'].value;
}
break;
case 'author': // G7
if (form['csd.author_rationale'] && form['csd.author_rationale'].value) {
currentParams.rationale = form['csd.author_rationale'].value;
}
break;
case 'g8': // G8
if (form['csd.g8_rationale'] && form['csd.g8_rationale'].value) {
currentParams.rationale = form['csd.g8_rationale'].value;
}
break;
case 'attack': // G10
currentParams.blanked = 'yes';
// it is actually blanked elsewhere in code, but setting the flag here
break;
case 'copyvio': // G12
if (form['csd.copyvio_url'] && form['csd.copyvio_url'].value) {
currentParams.url = form['csd.copyvio_url'].value;
}
if (form['csd.copyvio_url2'] && form['csd.copyvio_url2'].value) {
currentParams.url2 = form['csd.copyvio_url2'].value;
}
if (form['csd.copyvio_url3'] && form['csd.copyvio_url3'].value) {
currentParams.url3 = form['csd.copyvio_url3'].value;
}
break;
case 'afc': // G13
currentParams.ts = '$TIMESTAMP'; // to be replaced by the last revision timestamp when page is saved
break;
case 'redundantimage': // F1
if (form['csd.redundantimage_filename']) {
var redimage = form['csd.redundantimage_filename'].value;
if (!redimage || !redimage.trim()) {
alert('CSD F1: Please specify the filename of the other file.');
parameters = null;
return false;
}
currentParams.filename = /^\s*(Image|File):/i.test(redimage) ? redimage : 'File:' + redimage;
}
break;
case 'badfairuse': // F7
if (form['csd.badfairuse_rationale'] && form['csd.badfairuse_rationale'].value) {
currentParams.rationale = form['csd.badfairuse_rationale'].value;
}
break;
case 'commons': // F8
if (form['csd.commons_filename']) {
var filename = form['csd.commons_filename'].value;
if (filename && filename.trim() && filename !== Morebits.pageNameNorm) {
currentParams.filename = /^\s*(Image|File):/i.test(filename) ? filename : 'File:' + filename;
}
}
break;
case 'imgcopyvio': // F9
if (form['csd.imgcopyvio_url'] && form['csd.imgcopyvio_rationale']) {
var f9url = form['csd.imgcopyvio_url'].value;
var f9rationale = form['csd.imgcopyvio_rationale'].value;
if ((!f9url || !f9url.trim()) && (!f9rationale || !f9rationale.trim())) {
alert('CSD F9: You must enter a url or reason (or both) when nominating a file under F9.');
parameters = null;
return false;
}
if (form['csd.imgcopyvio_url'].value) {
currentParams.url = f9url;
}
if (form['csd.imgcopyvio_rationale'].value) {
currentParams.rationale = f9rationale;
}
}
break;
case 'foreign': // A2
if (form['csd.foreign_source']) {
var foreignlink = form['csd.foreign_source'].value;
if (!foreignlink || !foreignlink.trim()) {
alert('CSD A2: Please specify an interwiki link to the article of which this is a copy.');
parameters = null;
return false;
}
currentParams.source = foreignlink;
}
break;
case 'transwiki': // A5
if (form['csd.transwiki_location'] && form['csd.transwiki_location'].value) {
currentParams.location = form['csd.transwiki_location'].value;
}
break;
case 'a10': // A10
if (form['csd.a10_article']) {
var duptitle = form['csd.a10_article'].value;
if (!duptitle || !duptitle.trim()) {
alert('محس م۱۰: لطفاً نام مقالهٔ تکرار شده را مشخص کنید.'); // localized
parameters = null;
return false;
}
currentParams.article = duptitle;
}
break;
case 'policy': // T2
if (form['csd.policy_rationale'] && form['csd.policy_rationale'].value) {
currentParams.rationale = form['csd.policy_rationale'].value;
}
break;
case 'duplicatetemplate': // T3
if (form['csd.duplicatetemplate_2']) {
var t3template = form['csd.duplicatetemplate_2'].value;
if (!t3template || !t3template.trim()) {
alert('محس ال۳: لطفاً نام الگوی تکرار شده را مشخص کنید.'); // localized
parameters = null;
return false;
}
currentParams.ts = '~~~~~';
currentParams.template = t3template.replace(/^\s*Template:/i, '');
}
break;
case 'p1': // P1
if (form['csd.p1_criterion']) {
var criterion = form['csd.p1_criterion'].value;
if (!criterion || !criterion.trim()) {
alert('CSD P1: Please specify a single criterion.');
parameters = null;
return false;
}
currentParams.criterion = criterion;
}
break;
default:
break;
}
parameters.push(currentParams);
});
return parameters;
};
// Function for processing talk page notification template parameters
// key1/value1: for {{db-criterion-[notice|deleted]}} (via {{db-csd-[notice|deleted]-custom}})
// utparams.param: for {{db-[notice|deleted]-multiple}}
Twinkle.speedy.getUserTalkParameters = function twinklespeedyGetUserTalkParameters(normalized, parameters) {
var utparams = [];
// Special cases
if (normalized === 'db') {
utparams['2'] = parameters['1'];
} else if (normalized === 'g6') {
utparams.key1 = 'to';
utparams.value1 = Morebits.pageNameNorm;
} else if (normalized === 'g12') {
['url', 'url2', 'url3'].forEach(function(item, idx) {
if (parameters[item]) {
idx++;
utparams['key' + idx] = item;
utparams['value' + idx] = utparams[item] = parameters[item];
}
});
} else {
// Handle the rest
var param;
switch (normalized) {
case 'g4':
param = 'xfd';
break;
case 'a2':
param = 'source';
break;
case 'a5':
param = 'location';
break;
case 'a10':
param = 'article';
break;
case 'f9':
param = 'url';
break;
case 'p1':
param = 'criterion';
break;
default:
break;
}
// No harm in providing a usertalk template with the others' parameters
if (param && parameters[param]) {
utparams.key1 = param;
utparams.value1 = utparams[param] = parameters[param];
}
}
return utparams;
};
Twinkle.speedy.resolveCsdValues = function twinklespeedyResolveCsdValues(e) {
var values = (e.target.form ? e.target.form : e.target).getChecked('csd');
if (values.length === 0) {
alert('Please select a criterion!');
return null;
}
return values;
};
Twinkle.speedy.callback.evaluateSysop = function twinklespeedyCallbackEvaluateSysop(e) {
var form = e.target.form ? e.target.form : e.target;
if (e.target.type === 'checkbox' || e.target.type === 'text' ||
e.target.type === 'select') {
return;
}
var tag_only = form.tag_only;
if (tag_only && tag_only.checked) {
Twinkle.speedy.callback.evaluateUser(e);
return;
}
var values = Twinkle.speedy.resolveCsdValues(e);
if (!values) {
return;
}
var templateParams = Twinkle.speedy.getParameters(form, values);
if (!templateParams) {
return;
}
var normalizeds = values.map(function(value) {
return Twinkle.speedy.normalizeHash[value];
});
// analyse each criterion to determine whether to watch the page, prompt for summary, or notify the creator
var watchPage, promptForSummary;
normalizeds.forEach(function(norm) {
if (Twinkle.getPref('watchSpeedyPages').indexOf(norm) !== -1) {
watchPage = true;
}
if (Twinkle.getPref('promptForSpeedyDeletionSummary').indexOf(norm) !== -1) {
promptForSummary = true;
}
});
var warnusertalk = false;
if (form.warnusertalk.checked) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('warnUserOnSpeedyDelete').indexOf(norm) !== -1) {
if (norm === 'g6' && values[index] !== 'copypaste') {
return true;
}
warnusertalk = true;
return false; // break
}
});
}
var welcomeuser = false;
if (warnusertalk) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('welcomeUserOnSpeedyDeletionNotification').indexOf(norm) !== -1) {
welcomeuser = true;
return false; // break
}
});
}
var params = {
values: values,
normalizeds: normalizeds,
watch: watchPage,
deleteTalkPage: form.talkpage && form.talkpage.checked,
deleteRedirects: form.redirects.checked,
warnUser: warnusertalk,
welcomeuser: welcomeuser,
promptForSummary: promptForSummary,
templateParams: templateParams
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Twinkle.speedy.callbacks.sysop.main(params);
};
Twinkle.speedy.callback.evaluateUser = function twinklespeedyCallbackEvaluateUser(e) {
var form = e.target.form ? e.target.form : e.target;
if (e.target.type === 'checkbox' || e.target.type === 'text' ||
e.target.type === 'select') {
return;
}
var values = Twinkle.speedy.resolveCsdValues(e);
if (!values) {
return;
}
var templateParams = Twinkle.speedy.getParameters(form, values);
if (!templateParams) {
return;
}
// var multiple = form.multiple.checked;
var normalizeds = [];
$.each(values, function(index, value) {
var norm = Twinkle.speedy.normalizeHash[value];
normalizeds.push(norm);
});
// analyse each criterion to determine whether to watch the page/notify the creator
var watchPage = false;
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('watchSpeedyPages').indexOf(norm) !== -1) {
watchPage = true;
return false; // break
}
});
var notifyuser = false;
if (form.notify.checked) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('notifyUserOnSpeedyDeletionNomination').indexOf(norm) !== -1) {
if (norm === 'g6' && values[index] !== 'copypaste') {
return true;
}
notifyuser = true;
return false; // break
}
});
}
var welcomeuser = false;
if (notifyuser) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('welcomeUserOnSpeedyDeletionNotification').indexOf(norm) !== -1) {
welcomeuser = true;
return false; // break
}
});
}
var csdlog = false;
if (Twinkle.getPref('logSpeedyNominations')) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('noLogOnSpeedyNomination').indexOf(norm) === -1) {
csdlog = true;
return false; // break
}
});
}
var params = {
values: values,
normalizeds: normalizeds,
watch: watchPage,
usertalk: notifyuser,
welcomeuser: welcomeuser,
lognomination: csdlog,
requestsalt: form.salting.checked,
templateParams: templateParams
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'Tagging complete';
// Modules can't be tagged, follow standard at TfD and place on /doc subpage
params.scribunto = mw.config.get('wgPageContentModel') === 'Scribunto';
var wikipedia_page = params.scribunto ? new Morebits.wiki.page(mw.config.get('wgPageName') + '/doc', 'Tagging module documentation page') : new Morebits.wiki.page(mw.config.get('wgPageName'), 'Tagging page');
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.speedy.callbacks.user.main);
};
})(jQuery);
// </nowiki>
/* ===== twinkleprod.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinkleprod.js: PROD module
****************************************
* Mode of invocation: Tab ("PROD")
* Active on: Existing articles, files, books which are not redirects,
* and user pages in [[:Category:Wikipedia books (user books)]]
*/
Twinkle.prod = function twinkleprod() {
if (([0, 6].indexOf(mw.config.get('wgNamespaceNumber')) === -1)
|| !mw.config.get('wgCurRevisionId') || Morebits.wiki.isPageRedirect()) {
return;
}
Twinkle.addPortletLink(Twinkle.prod.callback, 'پیشنهاد حذف', 'tw-prod', 'پیشنهاد حذف طبق وو:پح'); // localized
};
// Used in edit summaries, for comparisons, etc.
var namespace;
Twinkle.prod.callback = function twinkleprodCallback() {
Twinkle.prod.defaultReason = Twinkle.getPref('prodReasonDefault');
switch (mw.config.get('wgNamespaceNumber')) {
case 0:
namespace = 'مقاله'; // localized
break;
case 6:
namespace = 'پرونده'; // localized
break;
}
var Window = new Morebits.simpleWindow(800, 410);
Window.setTitle('پیشنهاد حذف'); // localized
Window.setScriptName('توینکل'); // localiezd
var form = new Morebits.quickForm(Twinkle.prod.callback.evaluate);
Window.addFooterLink('سیاست پیشنهاد حذف', 'وو:پح'); // localized
// prod reason controls
var field = form.append({
type: 'field',
label: 'پارامترها', // localized
name: 'parameters'
});
field.append({
type: 'checkbox',
list: [
{
label: 'آگاه کردن سازندهٔ صفحه در صورت امکان', // localized
value: 'notify',
name: 'notify',
tooltip: "در صورت انتخاب این گزینه در صفحه بحث نویسنده مقاله، پیام اطلاعرسانی درج میشود", // localized
checked: true
}
]
});
field.append({
type: 'textarea',
name: 'reason',
label: 'دلیل برای پیشنهاد حذف:', // localized
value: Twinkle.prod.defaultReason
});
Window.addFooterLink('راهنمای توینکل', 'وو:توینکل/توضیحات'); // localized
form.append({ type: 'submit', label: 'پیشنهاد حذف' }); // localized
var result = form.render();
Window.setContent(result);
Window.display();
};
Twinkle.prod.callbacks = {
checkpriors: function(apiobj) {
var params = apiobj.params;
var ts = new Morebits.wiki.page(mw.config.get('wgPageName'));
ts.setFollowRedirect(true); // for NPP, and also because redirects are ineligible for PROD
ts.setCallbackParameters(params);
ts.setLookupNonRedirectCreator(true); // Look for author of first non-redirect revision
ts.lookupCreation(Twinkle.prod.callbacks.creationInfo);
},
creationInfo: function(pageobj) {
var params = pageobj.getCallbackParameters();
params.initialContrib = pageobj.getCreator();
params.creation = pageobj.getCreationTimestamp();
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'برچسب زدن تمام شد'; // localized
var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'برچسب زدن به صفحه'); // localized
wikipedia_page.setFollowRedirect(true); // for NPP, and also because redirects are ineligible for PROD
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.prod.callbacks.main);
},
main: function(pageobj) {
var statelem = pageobj.getStatusElement();
if (!pageobj.exists()) {
statelem.error("به نظر میآید که صفحه وجود ندارد. احتمالا قبلا حذف شده است."); // localized
return;
}
var text = pageobj.getPageText();
var params = pageobj.getCallbackParameters();
// Check for already existing deletion tags
var tag_re = /{{(?:db-?|delete|حذف سریع|حذف زماندار|پیشنهاد حذف|نظرخواهی برای حذف|article for deletion\/dated|ffd\b)|#invoke:RfD/i; // localized
if (tag_re.test(text)) {
statelem.warn('صفحه در حال حاضر برچسب حذف دارد. نامزدی پیشنهاد حذف متوقف شد'); // localized
return;
}
// Remove tags that become superfluous with this action
text = text.replace(/{{\s*(userspace draft|پیشنویس صفحه کاربری|mtc|(copy|move) to wikimedia commons|انتقال به (انبار|ویکیانبار)|(copy |move )?to ?commons)\s*(\|(?:{{[^{}]*}}|[^{}])*)?}}\s*/gi, '');
var prod_re = /{{\s*پیشنهاد حذف\s*[|}]/i; // localized
var summaryText;
if (!prod_re.test(text)) {
// Notification to first contributor
if (params.usertalk) {
// Disallow warning yourself
if (params.initialContrib === mw.config.get('wgUserName')) {
statelem.warn('خود شما (' + params.initialContrib + ') را ایجاد کردهاید؛ توقف اطلاعرسانی به کاربر'); // localized
if (Twinkle.getPref('logProdPages')) {
Twinkle.prod.callbacks.addToLog(params);
}
} else {
var notifytext = '\n== پیشنهاد حذف [[:' + Morebits.pageNameNorm + ']] ==\n' +
'صفحهٔ [[:' + Morebits.pageNameNorm + ']] برای حذف پیشنهاد شدهاست. دلیل: ' + params.reason + ' لطفاً اگر مخالفید نظرتان را بیان کنید. ~~~~\n'; // localized
var usertalkpage = new Morebits.wiki.page('بحث کاربر:' + params.initialContrib, 'اطلاعرسانی به سازنده صفحه (' + params.initialContrib + ')'); // localized
usertalkpage.setAppendText(notifytext);
usertalkpage.setEditSummary('اطلاعرسانی: پیشنهاد حذف [[:' + Morebits.pageNameNorm + ']].' + Twinkle.getPref('summaryAd')); // localized
usertalkpage.setCreateOption('recreate');
usertalkpage.setFollowRedirect(true);
usertalkpage.setCallbackParameters(params);
usertalkpage.append(function onNotifySuccess() {
// add nomination to the userspace log, if the user has enabled it
if (Twinkle.getPref('logProdPages')) {
params.logInitialContrib = params.initialContrib;
Twinkle.prod.callbacks.addToLog(params);
}
}, function onNotifyError() {
// if user could not be notified, log nomination without mentioning that notification was sent
if (Twinkle.getPref('logProdPages')) {
Twinkle.prod.callbacks.addToLog(params);
}
});
}
} else if (Twinkle.getPref('logProdPages')) { // If not notifying, log this PROD
Twinkle.prod.callbacks.addToLog(params);
}
summaryText = 'نامزد کردن ' + namespace + ' برای پیشنهاد حذف طبق [[وو:پح]].'; // localized
text = '{{پیشنهاد حذف|1=' + Morebits.string.formatReasonText(params.reason) + '}}\n' + text; // localized
}
// curate/patrol the page
if (Twinkle.getPref('markProdPagesAsPatrolled')) {
pageobj.triage();
}
pageobj.setPageText(text);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchProdPages'));
pageobj.setCreateOption('nocreate');
pageobj.save();
},
addToLog: function(params) {
var usl = new Morebits.userspaceLogger(Twinkle.getPref('prodLogPageName'));
usl.initialText =
"این صفحه فهرستی از مواردی که این کاربر با استفاده از [[وو:توینکل|توینکل]] برای [[وو:پح|پیشنهاد حذف]] نامزد کردهاست را نشان میدهد.\n\n" + // localized
'اگر نمیخواهید موارد جدید به این سیاهه اضافه شوند، آن را از طریق [[وو:توینکل تر|ترجیحات توینکل]] غیرفعال کنید' + // localized
'و اگر میخواهید سیاهه کلاً حذف شود آن را بر اساس معیار [[وو:محس#ک۱|ک۱]] نامزد حذف سریع کنید.\n'; // localized
var logText = '# [[:' + Morebits.pageNameNorm + ']]';
var summaryText;
// If a logged file is deleted but exists on commons, the wikilink will be blue, so provide a link to the log
logText += namespace === 'پرونده' ? ' ([{{fullurl:Special:Log|page=' + mw.util.wikiUrlencode(mw.config.get('wgPageName')) + '}} سیاهه]): ' : ': '; // localized
logText += 'پیشنهاد حذف'; // localized
if (params.logInitialContrib) {
logText += '; {{کاربر|' + params.logInitialContrib + '}} مطلع شد'; // localized
}
logText += ' ~~~~~\n';
logText += "#* '''دلیل''': " + params.reason + '\n'; // localized
summaryText = 'ثبت نامزدی [[:' + Morebits.pageNameNorm + ']] در سیاههٔ کاربری شما.'; // localized
usl.log(logText, summaryText + Twinkle.getPref('summaryAd'));
}
};
Twinkle.prod.callback.evaluate = function twinkleprodCallbackEvaluate(e) {
var form = e.target;
var params = {
usertalk: form.notify.checked,
reason: form.reason.value
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Twinkle.prod.callbacks.checkpriors({'params': params});
};
})(jQuery);
// </nowiki>
/* ===== twinkledeprod.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinkledeprod.js: Batch deletion of expired PRODs (sysops only)
****************************************
* Mode of invocation: Tab ("Deprod")
* Active on: Categories whose name contains "proposed_deletion"
*/
Twinkle.deprod = function() {
if (
!Morebits.userIsSysop ||
mw.config.get('wgNamespaceNumber') !== 14 ||
!(/پیشنهاد.?حذف/i).test(mw.config.get('wgPageName')) // localized
) {
return;
}
Twinkle.addPortletLink(Twinkle.deprod.callback, 'حذف پیشنهادهای حذف دستهجمعی', 'tw-deprod', 'صفحههای ایجاد شده در این رده را حذف کنید'); // localized
};
var concerns = {};
Twinkle.deprod.callback = function() {
var Window = new Morebits.simpleWindow(800, 400);
Window.setTitle('انجام حذف پیشنهادهای حذف'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('پیشنهاد حذف', 'وو:پح'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#deprod'); // localized
var form = new Morebits.quickForm(callback_commit);
var statusdiv = document.createElement('div');
statusdiv.style.padding = '15px'; // just so it doesn't look broken
Window.setContent(statusdiv);
Morebits.status.init(statusdiv);
Window.display();
var query = {
'action': 'query',
'generator': 'categorymembers',
'gcmtitle': mw.config.get('wgPageName'),
'gcmlimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
'gcmnamespace': '0|6|108|2', // mostly to ignore categories
'prop': [ 'info', 'revisions' ],
'rvprop': [ 'content' ],
'inprop': [ 'protection' ]
};
var statelem = new Morebits.status('گرفتن فهرست صفحهها'); // localized
var wikipedia_api = new Morebits.wiki.api('در حال بارگیری...', query, function(apiobj) { // localized
var $doc = $(apiobj.responseXML);
var $pages = $doc.find('page[ns!="6"]'); // all non-files
var list = [];
var re = /\{\{\s*پیشنهاد حذف/; // localized
$pages.each(function() {
var $page = $(this);
var title = $page.attr('title');
var content = $page.find('revisions rev').text();
var $editprot = $page.find('pr[type="edit"][level="sysop"]');
var isProtected = $editprot.length > 0;
var metadata = [];
var res = re.exec(content);
if (res) {
var parsed = Morebits.wikitext.template.parse(content, res.index);
concerns[title] = parsed.parameters['1'] || parsed.parameters['اهمیت'] || ''; // localized
metadata.push(concerns[title]);
}
if (isProtected) {
metadata.push('محافظت کامل' + // localized
($editprot.attr('expiry') === 'infinity' ? ' بیپایان' : '، انقضا در ' + $editprot.attr('expiry'))); // localized
}
list.push({
label: metadata.length ? '(' + metadata.join('; ') + ')' : '',
value: title,
checked: concerns[title] !== '',
style: isProtected ? 'color:red' : ''
});
});
apiobj.params.form.append({ type: 'header', label: 'صفحهها برای حذف' }); // localized
apiobj.params.form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', true);
}
});
apiobj.params.form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', false);
}
});
apiobj.params.form.append({
'type': 'checkbox',
'name': 'pages',
'list': list
});
apiobj.params.form.append({
'type': 'submit'
});
var rendered = apiobj.params.form.render();
apiobj.params.Window.setContent(rendered);
$(Morebits.quickForm.getElements(rendered, 'pages')).each(function(index, checkbox) {
var $checkbox = $(checkbox);
var link = Morebits.htmlNode('a', $checkbox.val());
link.setAttribute('class', 'deprod-page-link');
link.setAttribute('href', mw.util.getUrl($checkbox.val()));
link.setAttribute('target', '_blank');
$checkbox.next().prepend([link, ' ']);
});
}, statelem);
wikipedia_api.params = { form: form, Window: Window };
wikipedia_api.post();
};
var callback_commit = function(event) {
var pages = event.target.getChecked('pages');
Morebits.status.init(event.target);
var batchOperation = new Morebits.batchOperation('حذف صفحهها'); // localized
batchOperation.setOption('chunkSize', Twinkle.getPref('proddeleteChunks'));
batchOperation.setOption('preserveIndividualStatusLines', true);
batchOperation.setPageList(pages);
batchOperation.run(function(pageName) {
var params = { page: pageName, reason: concerns[page] };
var query = {
'action': 'query',
'titles': pageName,
'prop': 'redirects',
'rdlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
var wikipedia_api = new Morebits.wiki.api('دریافت تغییرمسیرها', query, callback_deleteRedirects); // localized
wikipedia_api.params = params;
wikipedia_api.post();
query = {
'action': 'query',
'titles': 'بحث:' + pageName
};
wikipedia_api = new Morebits.wiki.api('بررسی این که ' + pageName + ' صفحهٔ بحث دارد یا نه', query, // localized
callback_deleteTalk);
wikipedia_api.params = params;
wikipedia_api.post();
var page = new Morebits.wiki.page(pageName, 'در حال حذف صفحه ' + pageName); // localized
page.setEditSummary('حذف طبق [[ویکیواژه:پیشنهاد حذف|پیشنهاد حذف]]، دلیل صفحه: ' + (concerns[pageName] === undefined ? 'نامشخص' : concerns[pageName]) + Twinkle.getPref('deletionSummaryAd')); // localized
page.suppressProtectWarning();
page.deletePage(batchOperation.workerSuccess, batchOperation.workerFailure);
});
},
callback_deleteTalk = function(apiobj) {
var $doc = $(apiobj.responseXML);
var exists = $doc.find('page:not([missing])').length > 0;
if (!exists) {
// no talk page; forget about it
return;
}
var page = new Morebits.wiki.page('Talk:' + apiobj.params.page, 'حذف صفحهٔ بحث ' + apiobj.params.page); // localized
page.setEditSummary('[[وو:محس#ع۸|ع۸]]: [[راهنما:صفحه بحث|صفحهٔ بحث]] صفحهٔ حذف شده «' + apiobj.params.page + '»' + Twinkle.getPref('deletionSummaryAd')); // localized
page.deletePage();
},
callback_deleteRedirects = function(apiobj) {
var $doc = $(apiobj.responseXML);
$doc.find('redirects rd').each(function() {
var title = $(this).attr('title');
var page = new Morebits.wiki.page(title, 'حذف تغییر مسیر صفحه ' + title); // localized
page.setEditSummary('[[وو:محس#ع۸|ع۸]]: تغییر مسیر به صفحهٔ حذف شده «' + apiobj.params.page + '»' + Twinkle.getPref('deletionSummaryAd')); // localized
page.deletePage();
});
};
})(jQuery);
// </nowiki>
/* ===== twinkleprotect.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinkleprotect.js: Protect/RPP module
****************************************
* Mode of invocation: Tab ("PP"/"RPP")
* Active on: Non-special, non-MediaWiki pages
*/
// Note: a lot of code in this module is re-used/called by batchprotect.
Twinkle.protect = function twinkleprotect() {
if (mw.config.get('wgNamespaceNumber') < 0 || mw.config.get('wgNamespaceNumber') === 8) {
return;
}
Twinkle.addPortletLink(Twinkle.protect.callback, Morebits.userIsSysop ? 'حفاظت' : 'حفاظت', 'tw-rpp', // localized
Morebits.userIsSysop ? 'حفاظت صفحه' : 'درخواست حفاظت صفحه'); // localized
};
Twinkle.protect.callback = function twinkleprotectCallback() {
var Window = new Morebits.simpleWindow(620, 530);
Window.setTitle(Morebits.userIsSysop ? 'اعمال، درخواست یا برچسبگذاری حفاظت صفحه' : 'درخواست یا برچسبگذاری حفاظت صفحه'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('الگوهای حفاظت', 'الگو:الگوهای حفاظت'); // localized
Window.addFooterLink('سیاست حفاظت', 'وو:حفاظت'); // localized
Window.addFooterLink('راهنمای توینکل', 'وو:تل/توضیحات#حفاظت'); // localized
var form = new Morebits.quickForm(Twinkle.protect.callback.evaluate);
var actionfield = form.append({
type: 'field',
label: 'نوع کنش' // localized
});
if (Morebits.userIsSysop) {
actionfield.append({
type: 'radio',
name: 'actiontype',
event: Twinkle.protect.callback.changeAction,
list: [
{
label: 'محافظت صفحه', // localized
value: 'protect',
tooltip: 'اجرای حفاظت بر روی صفحه.', // localized
checked: true
}
]
});
}
actionfield.append({
type: 'radio',
name: 'actiontype',
event: Twinkle.protect.callback.changeAction,
list: [
{
label: 'درخواست حفاظت صفحه', // localized
value: 'request',
tooltip: 'اگر می خواهید ' + (Morebits.userIsSysop ? ' بجای اینکه خودتان شخصا محافظت را اجرا کنید،' : 'از طریق وو:دمص درخواست دهید'), // localized
checked: !Morebits.userIsSysop
},
{
label: 'برچسبزدن صفحه با الگوی حفاظت', // localized
value: 'tag',
tooltip: 'اگر مدیر حفاظتکننده فراموش کرده باشد برچسب بزند یا خودتان بدون افزودن برچسب حفاظت را اجرا کردهاید، از اینجا میتوانید برچسب را اعمال کنید', // localized
disabled: mw.config.get('wgArticleId') === 0 || mw.config.get('wgPageContentModel') === 'Scribunto'
}
]
});
form.append({ type: 'field', label: 'پیشفرض', name: 'field_preset' });
form.append({ type: 'field', label: '1', name: 'field1' });
form.append({ type: 'field', label: '2', name: 'field2' });
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
// We must init the controls
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.actiontype[0].dispatchEvent(evt);
// get current protection level asynchronously
Twinkle.protect.fetchProtectionLevel();
};
// Contains the current protection level in an object
// Once filled, it will look something like:
// { edit: { level: "sysop", expiry: <some date>, cascade: true }, ... }
Twinkle.protect.currentProtectionLevels = {};
// returns a jQuery Deferred object, usage:
// Twinkle.protect.fetchProtectingAdmin(apiObject, pageName, protect/stable).done(function(admin_username) { ...code... });
Twinkle.protect.fetchProtectingAdmin = function twinkleprotectFetchProtectingAdmin(api, pageName, protType, logIds) {
logIds = logIds || [];
return api.get({
format: 'json',
action: 'query',
list: 'logevents',
letitle: pageName,
letype: protType
}).then(function(data) {
// don't check log entries that have already been checked (e.g. don't go into an infinite loop!)
var event = data.query ? $.grep(data.query.logevents, function(le) {
return $.inArray(le.logid, logIds);
})[0] : null;
if (!event) {
// fail gracefully
return null;
} else if (event.action === 'move_prot' || event.action === 'move_stable') {
return twinkleprotectFetchProtectingAdmin(api, protType === 'protect' ? event.params.oldtitle_title : event.params.oldtitle, protType, logIds.concat(event.logid));
}
return event.user;
});
};
// mw.loader.getState('ext.flaggedRevs.review') returns null if the
// FlaggedRevs extension is not registered. Previously, this was done with
// wgFlaggedRevsParams, but after 1.34-wmf4 it is no longer exported if empty
// (https://gerrit.wikimedia.org/r/c/mediawiki/extensions/FlaggedRevs/+/508427)
Twinkle.protect.fetchProtectionLevel = function twinkleprotectFetchProtectionLevel() {
var api = new mw.Api();
var protectDeferred = api.get({
format: 'json',
indexpageids: true,
action: 'query',
list: 'logevents',
letype: 'protect',
letitle: mw.config.get('wgPageName'),
prop: mw.loader.getState('ext.flaggedRevs.review') ? 'info|flagged' : 'info',
inprop: 'protection',
titles: mw.config.get('wgPageName')
});
var stableDeferred = api.get({
format: 'json',
action: 'query',
list: 'logevents',
letype: 'stable',
letitle: mw.config.get('wgPageName')
});
var earlyDecision = [protectDeferred];
if (mw.loader.getState('ext.flaggedRevs.review')) {
earlyDecision.push(stableDeferred);
}
$.when.apply($, earlyDecision).done(function(protectData, stableData) {
// $.when.apply is supposed to take an unknown number of promises
// via an array, which it does, but the type of data returned varies.
// If there are two or more deferreds, it returns an array (of objects),
// but if there's just one deferred, it retuns a simple object.
// This is annoying.
protectData = $(protectData).toArray();
var pageid = protectData[0].query.pageids[0];
var page = protectData[0].query.pages[pageid];
var current = {}, adminEditDeferred;
$.each(page.protection, function(index, protection) {
if (protection.type !== 'aft') {
current[protection.type] = {
level: protection.level,
expiry: protection.expiry,
cascade: protection.cascade === ''
};
// logs report last admin who made changes to either edit/move/create protection, regardless if they only modified one of them
if (!adminEditDeferred) {
adminEditDeferred = Twinkle.protect.fetchProtectingAdmin(api, mw.config.get('wgPageName'), 'protect');
}
}
});
if (page.flagged) {
current.stabilize = {
level: page.flagged.protection_level,
expiry: page.flagged.protection_expiry
};
adminEditDeferred = Twinkle.protect.fetchProtectingAdmin(api, mw.config.get('wgPageName'), 'stable');
}
// show the protection level and log info
Twinkle.protect.hasProtectLog = !!protectData[0].query.logevents.length;
Twinkle.protect.hasStableLog = mw.loader.getState('ext.flaggedRevs.review') ? !!stableData[0].query.logevents.length : false;
Twinkle.protect.currentProtectionLevels = current;
if (adminEditDeferred) {
adminEditDeferred.done(function(admin) {
if (admin) {
$.each(['edit', 'move', 'create', 'stabilize'], function(i, type) {
if (Twinkle.protect.currentProtectionLevels[type]) {
Twinkle.protect.currentProtectionLevels[type].admin = admin;
}
});
}
Twinkle.protect.callback.showLogAndCurrentProtectInfo();
});
} else {
Twinkle.protect.callback.showLogAndCurrentProtectInfo();
}
});
};
Twinkle.protect.callback.showLogAndCurrentProtectInfo = function twinkleprotectCallbackShowLogAndCurrentProtectInfo() {
var currentlyProtected = !$.isEmptyObject(Twinkle.protect.currentProtectionLevels);
if (Twinkle.protect.hasProtectLog || Twinkle.protect.hasStableLog) {
var $linkMarkup = $('<span>');
if (Twinkle.protect.hasProtectLog) {
$linkMarkup.append(
$('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: mw.config.get('wgPageName'), type: 'protect'}) + '">protection log</a>'),
Twinkle.protect.hasStableLog ? $('<span> • </span>') : null
);
}
if (Twinkle.protect.hasStableLog) {
$linkMarkup.append($('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: mw.config.get('wgPageName'), type: 'stable'}) + '">pending changes log</a>)'));
}
Morebits.status.init($('div[name="hasprotectlog"] span')[0]);
Morebits.status.warn(
currentlyProtected ? 'محافظتهای پیشین' : 'این صفحه در گذشته مورد حفاظت قرار گرفتهاست', // localized
$linkMarkup[0]
);
}
Morebits.status.init($('div[name="currentprot"] span')[0]);
var protectionNode = [], statusLevel = 'info';
if (currentlyProtected) {
$.each(Twinkle.protect.currentProtectionLevels, function(type, settings) {
var label = type === 'stabilize' ? 'تغییرات در حال انتظار' : Morebits.string.toUpperCaseFirstChar(type); // localized
protectionNode.push($('<b>' + label + ': ' + settings.level + '</b>')[0]);
if (settings.expiry === 'infinity') {
protectionNode.push(' (بیپایان) '); // localized
} else {
protectionNode.push(' (زمان سرآمدن ' + new Date(settings.expiry).toUTCString() + ') '); // localized
}
if (settings.cascade) {
protectionNode.push('(آبشاری) '); // localized
}
if (settings.admin) {
var adminLink = '<a target="_blank" href="' + mw.util.getUrl('User talk:' + settings.admin) + '">' + settings.admin + '</a>';
protectionNode.push($('<span>توسط ' + adminLink + ' </span>')[0]); // localized
}
protectionNode.push($('<span> \u2022 </span>')[0]);
});
protectionNode = protectionNode.slice(0, -1); // remove the trailing bullet
statusLevel = 'warn';
} else {
protectionNode.push($('<b>محافظت نشده</b>')[0]); // localized
}
Morebits.status[statusLevel]('سطح محافظت فعلی', protectionNode); // localized
};
Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAction(e) {
var field_preset;
var field1;
var field2;
var isTemplate = mw.config.get('wgNamespaceNumber') === 10 || mw.config.get('wgNamespaceNumber') === 828;
switch (e.target.values) {
case 'protect':
field_preset = new Morebits.quickForm.element({ type: 'field', label: 'Preset', name: 'field_preset' });
field_preset.append({
type: 'select',
name: 'category',
label: 'انتخاب یک پیشفرض:', // localized
event: Twinkle.protect.callback.changePreset,
list: mw.config.get('wgArticleId') ?
Twinkle.protect.protectionTypes.filter(function(v) {
return isTemplate || v.label !== 'Template protection';
}) :
Twinkle.protect.protectionTypesCreate
});
field2 = new Morebits.quickForm.element({ type: 'field', label: 'گزینههای حفاظت', name: 'field2' }); // localized
field2.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback
field2.append({ type: 'div', name: 'hasprotectlog', label: ' ' });
// for existing pages
if (mw.config.get('wgArticleId')) {
field2.append({
type: 'checkbox',
name: 'editmodify',
event: Twinkle.protect.formevents.editmodify,
list: [
{
label: 'تغییر حفاظت در برابر ویرایش', // localized
value: 'editmodify',
tooltip: 'درصورت خاموشکردن این جعبه، سطح حفاظت از ویرایش و زمان سرآمدن به همان شکلی که هستند باقی میمانند', // localized
checked: true
}
]
});
var editlevel = field2.append({
type: 'select',
name: 'editlevel',
label: 'محافظت در برابر ویرایش:', // localized
event: Twinkle.protect.formevents.editlevel
});
editlevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
editlevel.append({
type: 'option',
label: 'تأییدشدهٔ خودکار', // localized
value: 'autoconfirmed'
});
editlevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed'
});
if (isTemplate) {
editlevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
}
editlevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
field2.append({
type: 'select',
name: 'editexpiry',
label: 'سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
// default expiry selection is conditionally set in Twinkle.protect.callback.changePreset
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'دلخواه', value: 'custom' } // localized
]
});
field2.append({
type: 'checkbox',
name: 'movemodify',
event: Twinkle.protect.formevents.movemodify,
list: [
{
label: 'تغییر حفاظت از انتقال', // localized
value: 'movemodify',
tooltip: 'اگر این جعبه خاموش باشد، سطح حفاظت از انتقال و زمان سرآمدن به همان شکلی که هستند باقیمیمانند', // localized
checked: true
}
]
});
var movelevel = field2.append({
type: 'select',
name: 'movelevel',
label: 'حفاظت دربرابر انتقال:', // localized
event: Twinkle.protect.formevents.movelevel
});
movelevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
movelevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed'
});
if (isTemplate) {
movelevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
}
movelevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
field2.append({
type: 'select',
name: 'moveexpiry',
label: 'سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
// default expiry selection is conditionally set in Twinkle.protect.callback.changePreset
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'دلخواه', value: 'custom' } // localized
]
});
// Namespaces hardcoded until [[phab:T218479]]
if (mw.loader.getState('ext.flaggedRevs.review') && (mw.config.get('wgNamespaceNumber') === 0 || mw.config.get('wgNamespaceNumber') === 4)) {
field2.append({
type: 'checkbox',
name: 'pcmodify',
event: Twinkle.protect.formevents.pcmodify,
list: [
{
label: 'تغییر حفاظت نیازمند بازبینی', // localized
value: 'pcmodify',
tooltip: 'اگر این جعبه خاموش باشد، سطح حفاظت نیازمند بازبینی و زمان سرآمدن به همان شکلی که هستند باقی میمانند', // localized
checked: true
}
]
});
var pclevel = field2.append({
type: 'select',
name: 'pclevel',
label: 'تغییرات درحال انتظار:', // localized
event: Twinkle.protect.formevents.pclevel
});
pclevel.append({
type: 'option',
label: 'هیچ', // localized
value: 'none'
});
pclevel.append({
type: 'option',
label: 'تغییرات در حال انتظار', // localized
value: 'autoconfirmed',
selected: true
});
field2.append({
type: 'select',
name: 'pcexpiry',
label: 'سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', selected: true, value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'دلخواه', value: 'custom' } // localized
]
});
}
} else { // for non-existing pages
var createlevel = field2.append({
type: 'select',
name: 'createlevel',
label: 'حفاظت در برابر ایجاد:', // localized
event: Twinkle.protect.formevents.createlevel
});
createlevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
if (mw.config.get('wgNamespaceNumber') !== 0) {
createlevel.append({
type: 'option',
label: 'تأیید خودکار', // localized
value: 'autoconfirmed'
});
}
if (isTemplate) {
createlevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
}
createlevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed',
selected: true
});
createlevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop'
});
field2.append({
type: 'select',
name: 'createexpiry',
label: 'سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', selected: true, value: 'indefinite' }, // localized
{ label: 'دلخواه', value: 'custom' } // localized
]
});
}
field2.append({
type: 'textarea',
name: 'protectReason',
label: 'دلیل (برای سیاههٔ محافظت):' // localized
});
if (!mw.config.get('wgArticleId') || mw.config.get('wgPageContentModel') === 'Scribunto') { // tagging isn't relevant for non-existing or module pages
break;
}
/* falls through */
case 'tag':
field1 = new Morebits.quickForm.element({ type: 'field', label: 'گزینههای برچسب زدن', name: 'field1' });
field1.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback
field1.append({ type: 'div', name: 'hasprotectlog', label: ' ' });
field1.append({
type: 'select',
name: 'tagtype',
label: 'انتخاب الگوی محافظت:', // localized
list: Twinkle.protect.protectionTags,
event: Twinkle.protect.formevents.tagtype
});
field1.append({
type: 'checkbox',
list: [
{
name: 'small',
label: 'کوچک (small=yes)', // localized
tooltip: 'استفاده از ویژگی |small=yes الگو، به شکلی که فقط قفل نمایش داده شود', // localized
checked: true
},
{
name: 'noinclude',
label: 'قراردادن الگوی حفاظت درون برچسب <noinclude>', // localized
tooltip: 'الگوی حفاظت را درون <noinclude> قرار میدهد تا تراگنجانش نشود', // localized
checked: mw.config.get('wgNamespaceNumber') === 10
}
]
});
break;
case 'request':
field_preset = new Morebits.quickForm.element({ type: 'field', label: 'نوع محافظت', name: 'field_preset' }); // localized
field_preset.append({
type: 'select',
name: 'category',
label: 'نوع و دلیل:', // localized
event: Twinkle.protect.callback.changePreset,
list: mw.config.get('wgArticleId') ? Twinkle.protect.protectionTypes : Twinkle.protect.protectionTypesCreate
});
field1 = new Morebits.quickForm.element({ type: 'field', label: 'گزینهها', name: 'field1' }); // localized
field1.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback
field1.append({ type: 'div', name: 'hasprotectlog', label: ' ' });
field1.append({
type: 'select',
name: 'expiry',
label: 'مدت: ', // localized
list: [
{ label: 'موقت', value: 'temporary' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: '', selected: true, value: '' }
]
});
field1.append({
type: 'textarea',
name: 'reason',
label: 'دلیل: ' // localized
});
break;
default:
alert("مشکلی در حین اجرای حفاظت توینکلی پیش آمد"); // localized
break;
}
var oldfield;
if (field_preset) {
oldfield = $(e.target.form).find('fieldset[name="field_preset"]')[0];
oldfield.parentNode.replaceChild(field_preset.render(), oldfield);
} else {
$(e.target.form).find('fieldset[name="field_preset"]').css('display', 'none');
}
if (field1) {
oldfield = $(e.target.form).find('fieldset[name="field1"]')[0];
oldfield.parentNode.replaceChild(field1.render(), oldfield);
} else {
$(e.target.form).find('fieldset[name="field1"]').css('display', 'none');
}
if (field2) {
oldfield = $(e.target.form).find('fieldset[name="field2"]')[0];
oldfield.parentNode.replaceChild(field2.render(), oldfield);
} else {
$(e.target.form).find('fieldset[name="field2"]').css('display', 'none');
}
if (e.target.values === 'protect') {
// fake a change event on the preset dropdown
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
e.target.form.category.dispatchEvent(evt);
// reduce vertical height of dialog
$(e.target.form).find('fieldset[name="field2"] select').parent().css({ display: 'inline-block', marginRight: '0.5em' });
}
// re-add protection level and log info, if it's available
Twinkle.protect.callback.showLogAndCurrentProtectInfo();
};
Twinkle.protect.formevents = {
editmodify: function twinkleprotectFormEditmodifyEvent(e) {
e.target.form.editlevel.disabled = !e.target.checked;
e.target.form.editexpiry.disabled = !e.target.checked || (e.target.form.editlevel.value === 'all');
e.target.form.editlevel.style.color = e.target.form.editexpiry.style.color = e.target.checked ? '' : 'transparent';
},
editlevel: function twinkleprotectFormEditlevelEvent(e) {
e.target.form.editexpiry.disabled = e.target.value === 'all';
},
movemodify: function twinkleprotectFormMovemodifyEvent(e) {
// sync move settings with edit settings if applicable
if (e.target.form.movelevel.disabled && !e.target.form.editlevel.disabled) {
e.target.form.movelevel.value = e.target.form.editlevel.value;
e.target.form.moveexpiry.value = e.target.form.editexpiry.value;
} else if (e.target.form.editlevel.disabled) {
e.target.form.movelevel.value = 'sysop';
e.target.form.moveexpiry.value = 'indefinite';
}
e.target.form.movelevel.disabled = !e.target.checked;
e.target.form.moveexpiry.disabled = !e.target.checked || (e.target.form.movelevel.value === 'all');
e.target.form.movelevel.style.color = e.target.form.moveexpiry.style.color = e.target.checked ? '' : 'transparent';
},
movelevel: function twinkleprotectFormMovelevelEvent(e) {
e.target.form.moveexpiry.disabled = e.target.value === 'all';
},
pcmodify: function twinkleprotectFormPcmodifyEvent(e) {
e.target.form.pclevel.disabled = !e.target.checked;
e.target.form.pcexpiry.disabled = !e.target.checked || (e.target.form.pclevel.value === 'none');
e.target.form.pclevel.style.color = e.target.form.pcexpiry.style.color = e.target.checked ? '' : 'transparent';
},
pclevel: function twinkleprotectFormPclevelEvent(e) {
e.target.form.pcexpiry.disabled = e.target.value === 'none';
},
createlevel: function twinkleprotectFormCreatelevelEvent(e) {
e.target.form.createexpiry.disabled = e.target.value === 'all';
},
tagtype: function twinkleprotectFormTagtypeEvent(e) {
e.target.form.small.disabled = e.target.form.noinclude.disabled = (e.target.value === 'none') || (e.target.value === 'noop');
}
};
Twinkle.protect.doCustomExpiry = function twinkleprotectDoCustomExpiry(target) {
var custom = prompt('یک زمان سرآمدن سفارشی وارد کنید. \n میتوانید زمانهای نسبی مثل «۱ دقیقه» یا «۱۹ روز» یا زمانمهر مطلق به شکل yyyymmddhhmm وارد کنید (به عنوان مثال "200602011405" نشان دهندهٔ اول فوریهٔ ۲۰۰۶، ساعت ۱۴:۰۵ جهانی است).', ''); // localized fixes might be necessary
if (custom) {
var option = document.createElement('option');
option.setAttribute('value', custom);
option.textContent = custom;
target.appendChild(option);
target.value = custom;
} else {
target.selectedIndex = 0;
}
};
Twinkle.protect.protectionTypes = [
{ label: 'خروج از حفاظت', value: 'unprotect' }, // localized
{
label: 'حفاظت کامل', // localized
list: [
{ label: 'کلی (کامل)', value: 'pp-protected' }, // localized
{ label: 'اختلاف نظر / جنگ ویرایشی (کامل)', value: 'pp-dispute' }, // localized
{ label: 'خرابکاری مداوم (کامل)', value: 'pp-vandalism' }, // localized
{ label: 'بحث کاربری بستهشده (کامل)', value: 'pp-usertalk' } // localized
]
},
{
label: 'حفاظت الگو', // localized
list: [
{ label: 'الگوی پرکاربرد', value: 'pp-template' } // localized
]
},
{
label: 'Extended confirmed protection',
list: [
{ label: 'تصمیم هیئت داوری', selected: true, value: 'pp-30-500-arb' }, // localized
{ label: 'خرابکاری پیدرپی', value: 'pp-30-500-vandalism' }, // localized
{ label: 'ویرایش فلجکننده', value: 'pp-30-500-disruptive' }, // localized
{ label: 'سرپیچی از سیاست زندگینامهٔ زندگان', value: 'pp-30-500-blp' }, // localized
{ label: 'زاپاسبازی', value: 'pp-30-500-sock' } // localized
]
},
{
label: 'Semi-protection',
list: [
{ label: 'کلی (نیمهحفاظت)', value: 'pp-semi-protected' }, // localized
{ label: 'خرابکاری ادامهدار (نیمهحفاظت)', selected: true, value: 'pp-semi-vandalism' }, // localized
{ label: 'ویرایش فلجکننده (نیمهحفاظت)', value: 'pp-semi-disruptive' }, // localized
{ label: 'افزودن مطالب بدون منبع (نیمه حفاظت)', value: 'pp-semi-unsourced' }, // localized
{ label: 'سرپیچی از سیاست زندگینامهٔ زندگان (نیمهحفاظت)', value: 'pp-semi-blp' }, // localized
{ label: 'زاپاسبازی (نیمهحفاظت)', value: 'pp-semi-sock' }, // localized
{ label: 'صفحهٔ بحث کاربر بستهشده (نیمهحفاظت)', value: 'pp-semi-usertalk' } // localized
]
},
{
label: 'تغییرات در حال انتظار', // localized
list: [
{ label: 'کلی', value: 'pp-pc-protected' }, // localized
{ label: 'خرابکاری پیدرپی', value: 'pp-pc-vandalism' }, // localized
{ label: 'ویرایش فلجکننده', value: 'pp-pc-disruptive' }, // localized
{ label: 'افزودن مطالب بیمنبع', value: 'pp-pc-unsourced' }, // localized
{ label: 'سرپیچی از سیاست زندگینامهٔ زندگان', value: 'pp-pc-blp' } // localized
]
},
{
label: 'حفاظت از انتقال', // localized
list: [
{ label: 'کلی (انتقال)', value: 'pp-move' }, // localized
{ label: 'اختلاف نظر/جنگ برسر عنوان (انتقال)', value: 'pp-move-dispute' }, // localized
{ label: 'خرابکاری از طریق انتقال (انتقال)', value: 'pp-move-vandalism' }, // localized
{ label: 'صفحهٔ پربازدید (انتقال)', value: 'pp-move-indef' } // localized
]
}
];
Twinkle.protect.protectionTypesCreate = [
{ label: 'خروج از محافظت', value: 'unprotect' }, // localized
{
label: 'محافظت در برابر ایجاد', // localized
list: [
{ label: 'کلی ({{pp-create}})', value: 'pp-create' }, // localized
{ label: 'نام نامناسب', value: 'pp-create-offensive' }, // localized
{ label: 'ایجادهای مجدد', selected: true, value: 'pp-create-salt' }, // localized
{ label: 'زندگینامهٔ زندگان که یه تازگی حذفشده', value: 'pp-create-blp' } // localized
]
}
];
// A page with both regular and PC protection will be assigned its regular
// protection weight plus 2
Twinkle.protect.protectionWeight = {
sysop: 40,
templateeditor: 30,
extendedconfirmed: 20,
autoconfirmed: 10,
flaggedrevs_autoconfirmed: 5, // Pending Changes protection alone
all: 0,
flaggedrevs_none: 0 // just in case
};
// NOTICE: keep this synched with [[MediaWiki:Protect-dropdown]]
// Also note: stabilize = Pending Changes level
Twinkle.protect.protectionPresetsInfo = {
'pp-protected': {
edit: 'sysop',
move: 'sysop',
reason: null
},
'pp-dispute': {
edit: 'sysop',
move: 'sysop',
reason: '[[وو:مناقشه|مناقشه بر سر محتوا/جنگ ویرایشی]]' // localized
},
'pp-vandalism': {
edit: 'sysop',
move: 'sysop',
reason: '[[وو:خ|خرابکاری]] ادامهدار' // localized
},
'pp-usertalk': {
edit: 'sysop',
move: 'sysop',
reason: '[[وو:حبک|استفادهٔ نادرست از صفحهٔ بحث کاربر در حین بندایش]]' // localized
},
'pp-template': {
edit: 'templateeditor',
move: 'templateeditor',
reason: '[[وو:حساس|الگوهای بسیار پرکاربرد]]' // localized
},
'pp-30-500-arb': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: '[[وو:قفل آبی|درخواست هیئت نظارت]]',
template: 'pp-30-500'
},
'pp-30-500-vandalism': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: ' [[وو:خ|خرابکاری]] ادامهدار از سوی حسابهای تأییدشده(یخودکار)', // localized
template: 'pp-30-500'
},
'pp-30-500-disruptive': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: '[[وو:اخلال|ویرایش اخلالگرانهٔ]] ادامهدار توسط کاربران تأیید شده', // localized
template: 'pp-30-500'
},
'pp-30-500-blp': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: 'نقض ادامهدار [[وو:زز|سیاست زندگینامه زندگان]] توسط کاربران تأیید شده', // localized
template: 'pp-30-500'
},
'pp-30-500-sock': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: 'Persistent [[وو:زاپاس|زاپاس بازی]]', // localized
template: 'pp-30-500'
},
'pp-semi-vandalism': {
edit: 'autoconfirmed',
reason: '[[وو:خرابکاری|خرابکاری]] ادامهدار', // localized
template: 'pp-vandalism'
},
'pp-semi-disruptive': {
edit: 'autoconfirmed',
reason: '[[وو:اخلال|ویرایش اخلالگرانهٔ]] ادامهدار', // localized
template: 'pp-protected'
},
'pp-semi-unsourced': {
edit: 'autoconfirmed',
reason: 'افزودن مداوم [[WP:INTREF|مطالب بدون منبع یا با منبع نامعتبر]]', // localized
template: 'pp-protected'
},
'pp-semi-blp': {
edit: 'autoconfirmed',
reason: 'Violations of the [[WP:BLP|biographies of living persons policy]]',
template: 'pp-blp'
},
'pp-semi-usertalk': {
edit: 'autoconfirmed',
move: 'autoconfirmed',
reason: '[[WP:PP#Talk-page protection|Inappropriate use of user talk page while blocked]]',
template: 'pp-usertalk'
},
'pp-semi-template': { // removed for now
edit: 'autoconfirmed',
move: 'autoconfirmed',
reason: '[[WP:High-risk templates|Highly visible template]]',
template: 'pp-template'
},
'pp-semi-sock': {
edit: 'autoconfirmed',
reason: 'Persistent [[WP:Sock puppetry|sock puppetry]]',
template: 'pp-sock'
},
'pp-semi-protected': {
edit: 'autoconfirmed',
reason: null,
template: 'pp-protected'
},
'pp-pc-vandalism': {
stabilize: 'autoconfirmed', // stabilize = Pending Changes
reason: '[[وو:خرابکاری|خرابکاری]] مداوم', // localized
template: 'pp-pc'
},
'pp-pc-disruptive': {
stabilize: 'autoconfirmed',
reason: '[[وو:اخلال|ویرایشهای اخلالگرانه]] ادامهدار', // localized
template: 'pp-pc'
},
'pp-pc-unsourced': {
stabilize: 'autoconfirmed',
reason: 'ادامهدار بودن [[WP:INTREF|افزودن مداوم مطالب بدون منبع یا منبع ضعیف]]', // localized
template: 'pp-pc'
},
'pp-pc-blp': {
stabilize: 'autoconfirmed',
reason: 'نقض [[وو:زنده|سیاست زندگینامه زندگان]]', // localized
template: 'pp-pc'
},
'pp-pc-protected': {
stabilize: 'autoconfirmed',
reason: null,
template: 'pp-pc'
},
'pp-move': {
move: 'sysop',
reason: null
},
'pp-move-dispute': {
move: 'sysop',
reason: '[[وو:قفل سبز|مناقشه بر سر نام]]' // localized
},
'pp-move-vandalism': {
move: 'sysop',
reason: '[[وو:قفل سبز|خرابکاری از طریق انتقال]]' // localized
},
'pp-move-indef': {
move: 'sysop',
reason: '[[وو:قفل سبز|صفحهٔ پربیننده]]' // localized
},
'unprotect': {
edit: 'all',
move: 'all',
stabilize: 'none',
create: 'all',
reason: null,
template: 'none'
},
'pp-create-offensive': {
create: 'sysop',
reason: '[[وو:نمک|نام نامناسب]]' // localized
},
'pp-create-salt': {
create: 'extendedconfirmed',
reason: '[[وو:نمک|ایجاد پیاپی]]' // localized
},
'pp-create-blp': {
create: 'extendedconfirmed',
reason: '[[WP:BLPDEL|Recently deleted BLP]]'
},
'pp-create': {
create: 'extendedconfirmed',
reason: '{{pp-create}}' // localized version to be decided, non-existing in source
}
};
Twinkle.protect.protectionTags = [
{
label: 'هیچ (حذف الگوهای حفاظت فعلی)', // localized
value: 'none'
},
{
label: 'هیچ (بدون حذف الگوهای حفاظت فعلی)', // localized
value: 'noop'
},
{
label: 'الگوهای حفاظت دربرابر ویرایش', // localized
list: [
{ label: '{{pp-vandalism}}: خرابکاری', value: 'pp-vandalism' }, // localized
{ label: '{{pp-dispute}}: مناقشه/جنگ ویرایشی', value: 'pp-dispute' }, // localized
{ label: '{{pp-blp}}: سرپیچی از سیاست زندگینامهٔ زندگان', value: 'pp-blp' }, // localized
{ label: '{{pp-sock}}: زاپاسبازی', value: 'pp-sock' }, // localized
{ label: '{{pp-template}}: الگوی حساس', value: 'pp-template' }, // localized
{ label: '{{pp-usertalk}}: صفحهٔ بحث کاربر بستهشده', value: 'pp-usertalk' }, // localized
{ label: '{{pp-protected}}: حفاظت کلی', value: 'pp-protected' }, // localized
{ label: '{{pp-semi-indef}}: نیمهحفاظت کلی و طولانیمدت', value: 'pp-semi-indef' }, // localized
{ label: '{{pp-30-500}}: نیمهحفاظت ویژه', value: 'pp-30-500' } // localized
]
},
{
label: 'الگوهای تغییرات درحال انتظار', // localized
list: [
{ label: '{{pp-pc}}: تغییرات در حال انتظار', value: 'pp-pc' } // localized
]
},
{
label: 'الگوهای حفاظت در برابر انتقال', // localized
list: [
{ label: '{{pp-move-dispute}}: مناقشه/جنگ بر سر انتقال', value: 'pp-move-dispute' }, // localized
{ label: '{{pp-move-vandalism}}: خرابکاری از طریق انتقال', value: 'pp-move-vandalism' }, // localized
{ label: '{{pp-move-indef}}: کلی و طولانیمدت', value: 'pp-move-indef' }, // localized
{ label: '{{pp-move}}: سایر', value: 'pp-move' } // localized
]
}
];
Twinkle.protect.callback.changePreset = function twinkleprotectCallbackChangePreset(e) {
var form = e.target.form;
var actiontypes = form.actiontype;
var actiontype;
for (var i = 0; i < actiontypes.length; i++) {
if (!actiontypes[i].checked) {
continue;
}
actiontype = actiontypes[i].values;
break;
}
if (actiontype === 'protect') { // actually protecting the page
var item = Twinkle.protect.protectionPresetsInfo[form.category.value];
if (mw.config.get('wgArticleId')) {
if (item.edit) {
form.editmodify.checked = true;
Twinkle.protect.formevents.editmodify({ target: form.editmodify });
form.editlevel.value = item.edit;
Twinkle.protect.formevents.editlevel({ target: form.editlevel });
form.editexpiry.value = '2 days';
} else {
form.editmodify.checked = false;
Twinkle.protect.formevents.editmodify({ target: form.editmodify });
}
if (item.move) {
form.movemodify.checked = true;
Twinkle.protect.formevents.movemodify({ target: form.movemodify });
form.movelevel.value = item.move;
Twinkle.protect.formevents.movelevel({ target: form.movelevel });
form.moveexpiry.value = '2 days';
} else {
form.movemodify.checked = false;
Twinkle.protect.formevents.movemodify({ target: form.movemodify });
}
if (form.pcmodify) {
if (item.stabilize) {
form.pcmodify.checked = true;
Twinkle.protect.formevents.pcmodify({ target: form.pcmodify });
form.pclevel.value = item.stabilize;
Twinkle.protect.formevents.pclevel({ target: form.pclevel });
} else {
form.pcmodify.checked = false;
Twinkle.protect.formevents.pcmodify({ target: form.pcmodify });
}
}
} else {
if (item.create) {
form.createlevel.value = item.create;
Twinkle.protect.formevents.createlevel({ target: form.createlevel });
}
}
var reasonField = actiontype === 'protect' ? form.protectReason : form.reason;
if (item.reason) {
reasonField.value = item.reason;
} else {
reasonField.value = '';
}
// sort out tagging options, disabled if nonexistent or lua
if (mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') !== 'Scribunto') {
if (form.category.value === 'unprotect') {
form.tagtype.value = 'none';
} else {
form.tagtype.value = item.template ? item.template : form.category.value;
}
Twinkle.protect.formevents.tagtype({ target: form.tagtype });
if (/template/.test(form.category.value)) {
form.noinclude.checked = true;
form.editexpiry.value = form.moveexpiry.value = form.pcexpiry.value = 'indefinite';
} else if (mw.config.get('wgNamespaceNumber') !== 10) {
form.noinclude.checked = false;
}
}
} else { // RPP request
if (form.category.value === 'unprotect') {
form.expiry.value = '';
form.expiry.disabled = true;
} else {
form.expiry.value = '';
form.expiry.disabled = false;
}
}
};
Twinkle.protect.callback.evaluate = function twinkleprotectCallbackEvaluate(e) {
var form = e.target;
var actiontypes = form.actiontype;
var actiontype;
for (var i = 0; i < actiontypes.length; i++) {
if (!actiontypes[i].checked) {
continue;
}
actiontype = actiontypes[i].values;
break;
}
var tagparams;
if (actiontype === 'tag' || (actiontype === 'protect' && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') !== 'Scribunto')) {
tagparams = {
tag: form.tagtype.value,
reason: (form.tagtype.value === 'pp-protected' || form.tagtype.value === 'pp-semi-protected' || form.tagtype.value === 'pp-move') && form.protectReason ? form.protectReason.value : null,
small: form.small.checked,
noinclude: form.noinclude.checked
};
}
switch (actiontype) {
case 'protect':
// protect the page
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'محافظت کامل شد'; // localized
var statusInited = false;
var thispage;
var allDone = function twinkleprotectCallbackAllDone() {
if (thispage) {
thispage.getStatusElement().info('done');
}
if (tagparams) {
Twinkle.protect.callbacks.taggingPageInitial(tagparams);
}
};
var stabilizeValues = {};
if (form.pclevel) {
stabilizeValues = {
pclevel: form.pclevel.value,
pcexpiry: form.pcexpiry.value,
protectReason: form.protectReason.value
};
}
var protectIt = function twinkleprotectCallbackProtectIt(next) {
thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), ' محافظت صفحه'); // localized
if (mw.config.get('wgArticleId')) {
if (form.editmodify.checked) {
thispage.setEditProtection(form.editlevel.value, form.editexpiry.value);
}
if (form.movemodify.checked) {
thispage.setMoveProtection(form.movelevel.value, form.moveexpiry.value);
}
} else {
thispage.setCreateProtection(form.createlevel.value, form.createexpiry.value);
thispage.setWatchlist(false);
}
if (form.protectReason.value) {
thispage.setEditSummary(form.protectReason.value);
} else {
alert('میبایست دلیلی برای حفاظت جهت ذکر در سیاههٔ محافظت ارائه دهید.'); // localized
return;
}
if (!statusInited) {
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
statusInited = true;
}
thispage.protect(next);
};
var stabilizeIt = function twinkleprotectCallbackStabilizeIt() {
if (thispage) {
thispage.getStatusElement().info('done');
}
thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Applying pending changes protection');
thispage.setFlaggedRevs(stabilizeValues.pclevel, stabilizeValues.pcexpiry);
if (stabilizeValues.protectReason) {
thispage.setEditSummary(stabilizeValues.protectReason);
} else {
alert('باید دلیلی برای درخواست ذکر کنید تا در سیاهه محافظت ثبت شود.'); // localized
return;
}
if (!statusInited) {
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
statusInited = true;
}
thispage.stabilize(allDone, function(error) {
if (error.errorCode === 'stabilize_denied') { // [[phab:T234743]]
thispage.getStatusElement().error('Failed trying to modify pending changes settings, likely due to a mediawiki bug. Other actions (tagging or regular protection) may have taken place. Please reload the page and try again.');
}
});
};
if ((form.editmodify && form.editmodify.checked) || (form.movemodify && form.movemodify.checked) ||
!mw.config.get('wgArticleId')) {
if (form.pcmodify && form.pcmodify.checked) {
protectIt(stabilizeIt);
} else {
protectIt(allDone);
}
} else if (form.pcmodify && form.pcmodify.checked) {
stabilizeIt();
} else {
alert("لطفا دستوری به توینکل دهید.\nاگر صرفا قصد برچسبزدن دارید، گزینهٔ 'برچسب زدن صفحه' را از بالاانتخاب کنید."); // localized
}
break;
case 'tag':
// apply a protection template
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.followRedirect = false;
Morebits.wiki.actionCompleted.notice = 'برچسب زدن کامل شد'; // localized
Twinkle.protect.callbacks.taggingPageInitial(tagparams);
break;
case 'request':
// file request at RFPP
var typename, typereason;
switch (form.category.value) {
case 'pp-dispute':
case 'pp-vandalism':
case 'pp-usertalk':
case 'pp-protected':
typename = 'full protection';
break;
case 'pp-template':
typename = 'template protection';
break;
case 'pp-30-500-arb':
case 'pp-30-500-vandalism':
case 'pp-30-500-disruptive':
case 'pp-30-500-blp':
case 'pp-30-500-sock':
typename = 'extended confirmed protection';
break;
case 'pp-semi-vandalism':
case 'pp-semi-disruptive':
case 'pp-semi-unsourced':
case 'pp-semi-usertalk':
case 'pp-semi-sock':
case 'pp-semi-blp':
case 'pp-semi-protected':
typename = 'semi-protection';
break;
case 'pp-pc-vandalism':
case 'pp-pc-blp':
case 'pp-pc-protected':
case 'pp-pc-unsourced':
case 'pp-pc-disruptive':
typename = 'pending changes';
break;
case 'pp-move':
case 'pp-move-dispute':
case 'pp-move-indef':
case 'pp-move-vandalism':
typename = 'move protection';
break;
case 'pp-create':
case 'pp-create-offensive':
case 'pp-create-blp':
case 'pp-create-salt':
typename = 'create protection';
break;
case 'unprotect':
var admins = $.map(Twinkle.protect.currentProtectionLevels, function(pl) {
return pl.admin ? 'User:' + pl.admin : null;
});
if (admins.length && !confirm('آیا قبلا با مدیران حفاظت کننده (' + $.unique(admins).join(', ') + ') تماس گرفتهاید؟')) { // localized
return false;
}
// otherwise falls through
default:
typename = 'unprotection';
break;
}
switch (form.category.value) {
case 'pp-dispute':
typereason = 'مناقشه بر سر محتوا/جنگ ویرایشی'; // localized
break;
case 'pp-vandalism':
case 'pp-semi-vandalism':
case 'pp-pc-vandalism':
case 'pp-30-500-vandalism':
typereason = ' [[وو:خ|خرابکاری]] ادامهدار'; // localized
break;
case 'pp-semi-disruptive':
case 'pp-pc-disruptive':
case 'pp-30-500-disruptive':
typereason = ' [[Wikipedia:Disruptive editing|ویرایش فلجکنندهٔ ]] ادامهدار'; // localized
break;
case 'pp-semi-unsourced':
case 'pp-pc-unsourced':
typereason = '[[WP:INTREF|مطالب بدون منبع]] افزودن ادامهدار'; // localized
break;
case 'pp-template':
typereason = '[[WP:HIGHRISK|الگوی حساس]]'; // localized
break;
case 'pp-30-500-arb':
typereason = '[[WP:30/500|تصمیم هیئت داوری]]'; // localized
break;
case 'pp-usertalk':
case 'pp-semi-usertalk':
typereason = 'استفادهٔ نادرست از صفحهٔ بحث کاربر در حین بندایش'; // localized
break;
case 'pp-semi-sock':
case 'pp-30-500-sock':
typereason = '[[وو:ز|زاپاسبازی]] ادامهدار'; // localized
break;
case 'pp-semi-blp':
case 'pp-pc-blp':
case 'pp-30-500-blp':
typereason = 'سرپیچی از سیاست زندگینامهٔ زندگان'; // localized
break;
case 'pp-move-dispute':
typereason = 'مناقشه/جنگ بر سر عنوان/انتقال'; // localized
break;
case 'pp-move-vandalism':
typereason = 'خرابکاری از طریق اناقال صفحات'; // localized
break;
case 'pp-move-indef':
typereason = 'صفحهٔ پربازدید'; // localized
break;
case 'pp-create-offensive':
typereason = 'نام نامناسب'; // localized
break;
case 'pp-create-blp':
typereason = 'زندگینامهٔ زندگان که به تازگی حذف شده'; // localized
break;
case 'pp-create-salt':
typereason = 'ایجادهای پیدرپی'; // localized
break;
default:
typereason = '';
break;
}
var reason = typereason;
if (form.reason.value !== '') {
if (typereason !== '') {
reason += '\u00A0\u2013 '; // U+00A0 NO-BREAK SPACE; U+2013 EN RULE
}
reason += form.reason.value;
}
if (reason !== '' && reason.charAt(reason.length - 1) !== '.') {
reason += '.';
}
var rppparams = {
reason: reason,
typename: typename,
category: form.category.value,
expiry: form.expiry.value
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
var rppName = 'ویکیواژه:درخواست محافظت از صفحه'; // localized
// Updating data for the action completed event
Morebits.wiki.actionCompleted.redirect = rppName;
Morebits.wiki.actionCompleted.notice = 'درخواست کامل شد، درحال بازگشت به صفحه بحث'; // localized
var rppPage = new Morebits.wiki.page(rppName, 'درحال درخواست برای محافظت از صفحه'); // localized
rppPage.setFollowRedirect(true);
rppPage.setCallbackParameters(rppparams);
rppPage.load(Twinkle.protect.callbacks.fileRequest);
break;
default:
alert('twinkleprotect: unknown kind of action');
break;
}
};
Twinkle.protect.callbacks = {
taggingPageInitial: function(tagparams) {
if (tagparams.tag === 'noop') {
Morebits.status.info(' اعمال الگوی حفاظت', 'کاری برای انجام نیست'); // localized
return;
}
var protectedPage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'برچسب زدن صفحه'); // localized
protectedPage.setCallbackParameters(tagparams);
protectedPage.load(Twinkle.protect.callbacks.taggingPage);
},
taggingPage: function(protectedPage) {
var params = protectedPage.getCallbackParameters();
var text = protectedPage.getPageText();
var tag, summary;
var oldtag_re = /\s*(?:<noinclude>)?\s*\{\{\s*(pp-[^{}]*?|protected|(?:t|v|s|p-|usertalk-v|usertalk-s|sb|move)protected(?:2)?|protected template|privacy protection)\s*?\}\}\s*(?:<\/noinclude>)?\s*/gi;
var re_result = oldtag_re.exec(text);
if (re_result) {
if (params.tag === 'none' || confirm('{{' + re_result[1] + '}} در صفحه یافت نشد. \nبرای حذف آن تأیید و برای نگهداشتن آن لغو را کلیک کنید.')) { // localized
text = text.replace(oldtag_re, '');
}
}
if (params.tag === 'none') {
summary = 'حذف الگوی حفاظت' + Twinkle.getPref('summaryAd'); // localized
} else {
tag = params.tag;
if (params.reason) {
tag += '|reason=' + params.reason;
}
if (params.small) {
tag += '|small=yes';
}
if (/^\s*#redirect/i.test(text)) { // redirect page
// Only tag if no {{rcat shell}} is found
if (!text.match(/{{(?:redr|this is a redirect|r(?:edirect)?(?:.?cat.*)?[ _]?sh)/i)) {
text = text.replace(/#REDIRECT ?(\[\[.*?\]\])(.*)/i, '#REDIRECT $1$2\n\n{{' + tag + '}}');
} else {
Morebits.status.info('پوستهٔ ردهٔ تغییر مسیر موجود است', 'کاری برای انجام وجود ندارد'); // localized
return;
}
} else if (params.noinclude) {
text = '<noinclude>{{' + tag + '}}</noinclude>' + text;
} else {
text = '{{' + tag + '}}\n' + text;
}
summary = 'افزودن {{' + params.tag + '}}' + Twinkle.getPref('summaryAd'); // localized
}
protectedPage.setEditSummary(summary);
protectedPage.setPageText(text);
protectedPage.setCreateOption('nocreate');
protectedPage.suppressProtectWarning(); // no need to let admins know they are editing through protection
protectedPage.save();
},
fileRequest: function(rppPage) {
var params = rppPage.getCallbackParameters();
var text = rppPage.getPageText();
var statusElement = rppPage.getStatusElement();
var rppRe = new RegExp('===\\s*(\\[\\[)?\\s*:?\\s*' + RegExp.escape(Morebits.pageNameNorm, true) + '\\s*(\\]\\])?\\s*===', 'm');
var tag = rppRe.exec(text);
var rppLink = document.createElement('a');
rppLink.setAttribute('href', mw.util.getUrl(rppPage.getPageName()));
rppLink.appendChild(document.createTextNode(rppPage.getPageName()));
if (tag) {
statusElement.error([ 'در حال حاضر درخواست حفاظتی برای این صفحه در ', rppLink, ' موجود است، ادامه نمیدهیم' ]); // localized
return;
}
var newtag = '=== [[:' + Morebits.pageNameNorm + ']] ===\n';
if (new RegExp('^' + RegExp.escape(newtag).replace(/\s+/g, '\\s*'), 'm').test(text)) {
statusElement.error([ 'در حال حاضر درخواست حفاظتی برای این صفحه در ', rppLink, ' موجود است، ادامه نمیدهیم' ]); // localized
return;
}
newtag += '* {{pagelinks|1=' + Morebits.pageNameNorm + '}}\n\n';
var words;
switch (params.expiry) {
case 'temporary':
words = 'موقت '; // localized
break;
case 'indefinite':
words = 'بیپایان '; // localized
break;
default:
words = '';
break;
}
words += params.typename;
newtag += "'''" + Morebits.string.toUpperCaseFirstChar(words) + (params.reason !== '' ? ":''' " +
Morebits.string.formatReasonText(params.reason) : ".'''") + ' ~~~~';
// If either protection type results in a increased status, then post it under increase
// else we post it under decrease
var increase = false;
var protInfo = Twinkle.protect.protectionPresetsInfo[params.category];
// function to compute protection weights (see comment at Twinkle.protect.protectionWeight)
var computeWeight = function(mainLevel, stabilizeLevel) {
var result = Twinkle.protect.protectionWeight[mainLevel || 'all'];
if (stabilizeLevel) {
if (result) {
if (stabilizeLevel.level === 'autoconfirmed') {
result += 2;
}
} else {
result = Twinkle.protect.protectionWeight['flaggedrevs_' + stabilizeLevel];
}
}
return result;
};
// compare the page's current protection weights with the protection we are requesting
var editWeight = computeWeight(Twinkle.protect.currentProtectionLevels.edit &&
Twinkle.protect.currentProtectionLevels.edit.level,
Twinkle.protect.currentProtectionLevels.stabilize &&
Twinkle.protect.currentProtectionLevels.stabilize.level);
if (computeWeight(protInfo.edit, protInfo.stabilize) > editWeight ||
computeWeight(protInfo.move) > computeWeight(Twinkle.protect.currentProtectionLevels.move &&
Twinkle.protect.currentProtectionLevels.move.level) ||
computeWeight(protInfo.create) > computeWeight(Twinkle.protect.currentProtectionLevels.create &&
Twinkle.protect.currentProtectionLevels.create.level)) {
increase = true;
}
var reg;
if (increase) {
reg = /(\n==\s*Current requests for reduction in protection level\s*==)/;
} else {
reg = /(\n==\s*Current requests for edits to a protected page\s*==)/;
}
var originalTextLength = text.length;
text = text.replace(reg, '\n' + newtag + '\n$1');
if (text.length === originalTextLength) {
var linknode = document.createElement('a');
linknode.setAttribute('href', mw.util.getUrl('Wikipedia:Twinkle/Fixing RPP'));
linknode.appendChild(document.createTextNode('How to fix RPP'));
statusElement.error([ 'عنوانی مناسب در وو:دمص یافت نشد. برای حل این مشکل ', linknode, ' را ببینید' ]); // localized
return;
}
statusElement.status('افزودن درخواست جدید...'); // localized
rppPage.setEditSummary('/* ' + Morebits.pageNameNorm + ' */ Requesting ' + params.typename + (params.typename === 'pending changes' ? ' on [[:' : ' of [[:') +
Morebits.pageNameNorm + ']].' + Twinkle.getPref('summaryAd'));
rppPage.setPageText(text);
rppPage.setCreateOption('recreate');
rppPage.save();
}
};
})(jQuery);
// </nowiki>
/* ===== friendlytag.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** friendlytag.js: Tag module
****************************************
* Mode of invocation: Tab ("Tag")
* Active on: Existing articles and drafts; file pages with a corresponding file
* which is local (not on Commons); all redirects
*/
Twinkle.tag = function friendlytag() {
// redirect tagging
if (Morebits.wiki.isPageRedirect()) {
Twinkle.tag.mode = 'redirect';
Twinkle.addPortletLink(Twinkle.tag.callback, 'برچسب', 'friendly-tag', 'تغییرمسیر برچسب'); // localized
// file tagging
} else if (mw.config.get('wgNamespaceNumber') === 6 && !document.getElementById('mw-sharedupload') && document.getElementById('mw-imagepage-section-filehistory')) {
Twinkle.tag.mode = 'file';
Twinkle.addPortletLink(Twinkle.tag.callback, 'برچسب', 'friendly-tag', 'زدن برچسبهای نگهداری به پرونده'); // localized
// article/draft article tagging
} else if ([0, 118].indexOf(mw.config.get('wgNamespaceNumber')) !== -1 && mw.config.get('wgCurRevisionId')) {
Twinkle.tag.mode = 'article';
// Can't remove tags when not viewing current version
Twinkle.tag.canRemove = (mw.config.get('wgCurRevisionId') === mw.config.get('wgRevisionId')) &&
// Disabled on latest diff because the diff slider could be used to slide
// away from the latest diff without causing the script to reload
!mw.config.get('wgDiffNewId');
Twinkle.addPortletLink(Twinkle.tag.callback, 'برچسب', 'friendly-tag', 'افزودن یا حذف برچسبهای نگهداری مقاله'); // localized
}
};
Twinkle.tag.checkedTags = [];
Twinkle.tag.callback = function friendlytagCallback() {
var Window = new Morebits.simpleWindow(630, Twinkle.tag.mode === 'article' ? 500 : 400);
Window.setScriptName('Twinkle');
// anyone got a good policy/guideline/info page/instructional page link??
Window.addFooterLink('Twinkle help', 'WP:TW/DOC#tag');
var form = new Morebits.quickForm(Twinkle.tag.callback.evaluate);
form.append({
type: 'input',
label: 'پالایش سریع: ', // localized
name: 'quickfilter',
size: '30px',
event: function twinkletagquickfilter() {
// flush the DOM of all existing underline spans
$allCheckboxDivs.find('.search-hit').each(function(i, e) {
var label_element = e.parentElement;
// This would convert <label>Hello <span class=search-hit>wo</span>rld</label>
// to <label>Hello world</label>
label_element.innerHTML = label_element.textContent;
});
if (this.value) {
$allCheckboxDivs.hide();
$allHeaders.hide();
var searchString = this.value;
var searchRegex = new RegExp(mw.util.escapeRegExp(searchString), 'i');
$allCheckboxDivs.find('label').each(function () {
var label_text = this.textContent;
var searchHit = searchRegex.exec(label_text);
if (searchHit) {
var range = document.createRange();
var textnode = this.childNodes[0];
range.selectNodeContents(textnode);
range.setStart(textnode, searchHit.index);
range.setEnd(textnode, searchHit.index + searchString.length);
var underline_span = $('<span>').addClass('search-hit').css('text-decoration', 'underline')[0];
range.surroundContents(underline_span);
this.parentElement.style.display = 'block'; // show
}
});
} else {
$allCheckboxDivs.show();
$allHeaders.show();
}
}
});
switch (Twinkle.tag.mode) {
case 'article':
Window.setTitle('Article maintenance tagging');
form.append({
type: 'select',
name: 'sortorder',
label: 'مشاهده این فهرست:', // localized
tooltip: 'میتوانید ترتیب پیشفرض نمایش را در ترجیحات توینکل خود تغییر دهید (وو:توینکل تر).', // localized
event: Twinkle.tag.updateSortOrder,
list: [
{ type: 'option', value: 'cat', label: 'بر پایه رده', selected: Twinkle.getPref('tagArticleSortOrder') === 'cat' }, // localized
{ type: 'option', value: 'alpha', label: 'بهترتیب الفبا', selected: Twinkle.getPref('tagArticleSortOrder') === 'alpha' } // localized
]
});
if (!Twinkle.tag.canRemove) {
var divElement = document.createElement('div');
divElement.innerHTML = 'برای حذف برچسبهای موجود، لطفاً منوی برچسب را از نسخهٔ کنونی مقاله باز کنید'; // localized
form.append({
type: 'div',
name: 'untagnotice',
label: divElement
});
}
form.append({
type: 'div',
id: 'tagWorkArea',
className: 'morebits-scrollbox',
style: 'max-height: 28em'
});
form.append({
type: 'checkbox',
list: [
{
label: 'گروهبندی برچسبها در {{مشکلات متعدد}}، در صورت امکان', // localized
value: 'group',
name: 'group',
tooltip: 'در صورتی که در حال افزودن دو یا چند الگویی که توسط {{مشکلات متعدد}} پشتیبانی میشوند هستید و این گزینه نیز انتخاب شدهاست، تمامی الگوهای پشتیبانیشده در درون الگوی {{مشکلات متعدد}} گروهبندی خواهند شد.', // localized
checked: Twinkle.getPref('groupByDefault')
}
]
});
form.append({
type: 'input',
label: 'دلیل',
name: 'reason',
tooltip: 'دلیل اختیاری برای پیوستشدن به خلاصه ویرایش. ذکر دلیل بههنگام حذف برچسبها توصیه میشود.', // localized
size: '60px'
});
break;
case 'file':
Window.setTitle('File maintenance tagging');
form.append({ type: 'header', label: 'برچسبهای مشکلات مجوز و منبعدهی' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.licenseList });
form.append({ type: 'header', label: 'برچسبهای مرتبط با ویکیانبار' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.commonsList });
form.append({ type: 'header', label: 'برچسبهای تمیزکاری' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.cleanupList });
form.append({ type: 'header', label: 'برچسبهای کیفیت تصویر' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.qualityList });
form.append({ type: 'header', label: 'برچسبهای جایگزینی' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.replacementList });
if (Twinkle.getPref('customFileTagList').length) {
form.append({ type: 'header', label: 'برچسبهای سفارشی' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.getPref('customFileTagList') });
}
break;
case 'redirect':
Window.setTitle('Redirect tagging');
form.append({ type: 'header', label: 'Spelling, misspelling, tense and capitalization templates' });
form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.spellingList });
form.append({ type: 'header', label: 'الگوهای نام جایگزین' }); // localized
form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.alternativeList });
form.append({ type: 'header', label: 'الگوهای تغییرمسیر گوناگون و مدیریتی' }); // localized
form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.administrativeList });
if (Twinkle.getPref('customRedirectTagList').length) {
form.append({ type: 'header', label: 'برچسبهای سفارشی' }); // localized
form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.getPref('customRedirectTagList') });
}
break;
default:
alert('Twinkle.tag: unknown mode ' + Twinkle.tag.mode);
break;
}
if (document.getElementsByClassName('patrollink').length) {
form.append({
type: 'checkbox',
list: [
{
label: 'علامت زدن صفحه بهعنوان گشتخورده/بازبینیشده', // localized
value: 'patrolPage',
name: 'patrolPage',
checked: Twinkle.getPref('markTaggedPagesAsPatrolled')
}
]
});
}
form.append({ type: 'submit', className: 'tw-tag-submit' });
var result = form.render();
Window.setContent(result);
Window.display();
// for quick filter:
$allCheckboxDivs = $(result).find('[name$=Tags]').parent();
$allHeaders = $(result).find('h5');
result.quickfilter.focus(); // place cursor in the quick filter field as soon as window is opened
result.quickfilter.autocomplete = 'off'; // disable browser suggestions
result.quickfilter.addEventListener('keypress', function(e) {
if (e.keyCode === 13) { // prevent enter key from accidentally submitting the form
e.preventDefault();
return false;
}
});
if (Twinkle.tag.mode === 'article') {
Twinkle.tag.alreadyPresentTags = [];
if (Twinkle.tag.canRemove) {
// Look for existing maintenance tags in the lead section and put them in array
// All tags are HTML table elements that are direct children of .mw-parser-output,
// except when they are within {{multiple issues}}
$('.mw-parser-output').children().each(function parsehtml(i, e) {
// break out on encountering the first heading, which means we are no
// longer in the lead section
if (e.tagName === 'H2') {
return false;
}
// The ability to remove tags depends on the template's {{ambox}} |name=
// parameter bearing the template's correct name (preferably) or a name that at
// least redirects to the actual name
// All tags have their first class name as "box-" + template name
if (e.className.indexOf('box-') === 0) {
if (e.classList[0] === 'box-Multiple_issues') {
$(e).find('.ambox').each(function(idx, e) {
var tag = e.classList[0].slice(4).replace(/_/g, ' ');
Twinkle.tag.alreadyPresentTags.push(tag);
});
return true; // continue
}
var tag = e.classList[0].slice(4).replace(/_/g, ' ');
Twinkle.tag.alreadyPresentTags.push(tag);
}
});
// {{Uncategorized}} and {{Improve categories}} are usually placed at the end
if ($('.box-Uncategorized').length) {
Twinkle.tag.alreadyPresentTags.push('Uncategorized');
}
if ($('.box-Improve_categories').length) {
Twinkle.tag.alreadyPresentTags.push('Improve categories');
}
}
// Add status text node after Submit button
var statusNode = document.createElement('small');
statusNode.id = 'tw-tag-status';
Twinkle.tag.status = {
// initial state; defined like this because these need to be available for reference
// in the click event handler
numAdded: 0,
numRemoved: 0
};
$('button.tw-tag-submit').after(statusNode);
// fake a change event on the sort dropdown, to initialize the tag list
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.sortorder.dispatchEvent(evt);
} else {
// Redirects and files: Add a link to each template's description page
Morebits.quickForm.getElements(result, Twinkle.tag.mode + 'Tags').forEach(generateLinks);
}
};
// $allCheckboxDivs and $allHeaders are defined globally, rather than in the
// quickfilter event function, to avoid having to recompute them on every keydown
var $allCheckboxDivs, $allHeaders;
Twinkle.tag.updateSortOrder = function(e) {
var form = e.target.form;
var sortorder = e.target.value;
Twinkle.tag.checkedTags = form.getChecked('articleTags') || [];
var container = new Morebits.quickForm.element({ type: 'fragment' });
// function to generate a checkbox, with appropriate subgroup if needed
var makeCheckbox = function(tag, description) {
var checkbox = { value: tag, label: '{{' + tag + '}}: ' + description };
if (Twinkle.tag.checkedTags.indexOf(tag) !== -1) {
checkbox.checked = true;
}
switch (tag) {
case 'Cleanup':
checkbox.subgroup = {
name: 'cleanup',
type: 'input',
label: 'دلیل مشخص برای اینکه چرا به تمیزکاری نیاز است: ', // localized
tooltip: 'الزامی.', // localized
size: 35
};
break;
case 'Close paraphrasing':
checkbox.subgroup = {
name: 'closeParaphrasing',
type: 'input',
label: 'منبع: ', // localized
tooltip: 'منبعی که بهطور نزدیک بازنویسی شدهاست' // localized
};
break;
case 'Copy edit':
checkbox.subgroup = {
name: 'copyEdit',
type: 'input',
label: '"این مقاله ممکن است نیازمند ویرایستاری باشد..." ', // localized
tooltip: 'بهعنوان مثال، "املای یکپارچه". انتخابی.', // localized
size: 35
};
break;
case 'Copypaste':
checkbox.subgroup = {
name: 'copypaste',
type: 'input',
label: 'نشانی منبع: ', // localized
tooltip: 'اگر مشخص است.', // localized
size: 50
};
break;
case 'Expand language':
checkbox.subgroup = [ {
name: 'expandLanguageLangCode',
type: 'input',
label: 'شناسه زبان: ', // localized
tooltip: 'شناسهٔ زبانی که مقاله باید از روی آن گسترش دادهشود' // localized
}, {
name: 'expandLanguageArticle',
type: 'input',
label: 'عنوان مقاله: ', // localized
tooltip: 'عنوان مقالهای که گسترش از روی آن انجام میشود، بدون پیشوند میانویکی' // localized
}
];
break;
case 'Expert needed':
checkbox.subgroup = [
{
name: 'expertNeeded',
type: 'input',
label: 'عنوان ویکیپروژهٔ مرتبط: ', // localized
tooltip: 'بهطور انتخابی، نام ویکیپروژهای که میتواند به جذب یک کاربر باتجربه کمک کند را وارد کنید. بدون پیشوند «ویکیپروژه».' // localized
},
{
name: 'expertNeededReason',
type: 'input',
label: 'دلیل: ', // localized
tooltip: 'توضیح کوتاه برای توصیف مشکل. پیوند به دلیل یا بحث الزامی است.' // localized
},
{
name: 'expertNeededTalk',
type: 'input',
label: 'ریسهٔ بحث: ', // localized
tooltip: 'عنوان بخشی از صفحهٔ بحث این مقاله که بحث دربارهٔ مشکل در آن جریان دارد. نیاز به درج پیوند نیست، تنها عنوان بخش را وارد کنید. پیوند به دلیل یا بحث الزامی است.' // localized
}
];
break;
case 'Globalize':
checkbox.subgroup = {
name: 'globalizeRegion',
type: 'input',
label: 'منطقه یا کشورِ بیش از اندازه معرفیشده' // localized
};
break;
case 'History merge':
checkbox.subgroup = [
{
name: 'histmergeOriginalPage',
type: 'input',
label: 'مقالهٔ دیگر: ', // localized
tooltip: 'عنوان صفحهای که میتواند در این مقاله ادغام شود (الزامی).' // localized
},
{
name: 'histmergeReason',
type: 'input',
label: 'دلیل: ', // localized
tooltip: 'توضیح کوتاه که دلیل نیاز به ادغام تاریخچه را بیان کند. بهتر است با عبارت «زیرا» آغاز شده و با یک نقطه پایان یابد.' // localized
},
{
name: 'histmergeSysopDetails',
type: 'input',
label: 'جزئیات بیشتر: ', // localized
tooltip: 'برای موارد پیچیده، راهنماییهای بیشتری را در اختیار مدیر بررسیکننده قرار دهید.' // localized
}
];
break;
case 'Merge':
case 'Merge from':
case 'Merge to':
var otherTagName = 'Merge';
switch (tag) {
case 'Merge from':
otherTagName = 'Merge to';
break;
case 'Merge to':
otherTagName = 'Merge from';
break;
// no default
}
checkbox.subgroup = [
{
name: 'mergeTarget',
type: 'input',
label: 'مقاله(های) دیگر: ', // localized
tooltip: 'اگر قصد دارید مقالههای متعدد را مشخص کنید، آنها را با نویسهٔ لوله از هم جدا کنید: مقالهٔ اول|مقالهٔ دوم' // localized
},
{
name: 'mergeTagOther',
type: 'checkbox',
list: [
{
label: 'مقالهٔ دیگر را با یک برچسب {{' + otherTagName + '}} مشخص کنید', // localized
checked: true,
tooltip: 'فقط در صورتی که تنها عنوان یک مقاله وارد شدهباشد، در دسترس است.' // localized
}
]
}
];
if (mw.config.get('wgNamespaceNumber') === 0) {
checkbox.subgroup.push({
name: 'mergeReason',
type: 'textarea',
label: 'دلیل اصلی برای ادغام (بر روی صفحهٔ بحث' +
(tag === 'Merge to' ? 'the other article\'s' : 'this article\'s') + ' قرار خواهد گرفت):', // localized
tooltip: 'انتخابی، اما بهشدت توصیهشده. در صورت عدم تمایل، خالی بگذارید. فقط در صورتی که تنها عنوان یک مقاله وارد شدهباشد، در دسترس است.' // localized
});
}
break;
case 'Not English':
case 'Rough translation':
checkbox.subgroup = [
{
name: 'translationLanguage',
type: 'input',
label: 'زبان مقاله (اگر مشخص است): ', // localized
tooltip: 'برای راهنمایی، مرور [[en:WP:LRC]] را در نظر داشتهباشید. اگر مقاله را در صفحهٔ مقالههای نیازمند ترجمه فهرست میکنید، لطفاً از خالیگذاشتن این جعبه خودداری کنید؛ مگر آنکه کاملاً نامطمؤن باشید.' // localized
}
];
if (tag === 'Not English') {
checkbox.subgroup.push({
name: 'translationNotify',
type: 'checkbox',
list: [
{
label: 'اطلاعرسانی به سازندهٔ مقاله', // localized
checked: true,
tooltip: "الگوی {{uw-notenglish}} را در صفحهٔ بحث سازندهٔ مقاله قرار میدهد." // localized
}
]
});
}
if (mw.config.get('wgNamespaceNumber') === 0) {
checkbox.subgroup.push({
name: 'translationPostAtPNT',
type: 'checkbox',
list: [
{
label: 'فهرستکردن این مقاله در ویکیپدیا:صفحههای نیازمند ترجمه به فارسی (PNT)', // localized
checked: true
}
]
});
checkbox.subgroup.push({
name: 'translationComments',
type: 'textarea',
label: 'توضیح بیشتر برای ارسال به ویکیپدیا:صفحههای نیازمند ترجمه به فارسی', // localized
tooltip: 'انتخابی، و متناسب تنها در صورتی که "فهرستکردن این مقاله..." در بالا انتخاب شدهباشد.' // localized
});
}
break;
case 'Notability':
checkbox.subgroup = {
name: 'notability',
type: 'select',
list: [
{ label: "{{سرشناسی}}: موضوع مقاله ممکن است رهنمود اصلی سرشناسی را احراز نکند", value: 'none' }, // localized
{ label: '{{سرشناسی|دانشگاهیان}}: رهنمود سرشناسی دانشگاهیان', value: 'Academics' }, // localized
{ label: '{{سرشناسی|اجرام آسمانی}}: رهنمود سرشناسی اجرام آسمانی', value: 'Astro' }, // localized
{ label: '{{سرشناسی|افراد}}: رهنمود سرشناسی زندگینامه افراد', value: 'Biographies' }, // localized
{ label: '{{سرشناسی|کتابها}}: رهنمود سرشناسی کتاب', value: 'Books' }, // localized
{ label: '{{سرشناسی|سازمانها}}: رهنمودهای سرشناسی شرکتها و سازمانها', value: 'Companies' }, // localized
{ label: '{{سرشناسی|رویدادها}}: رهنمود سرشناسی برای رویدادها', value: 'Events' }, // localized
{ label: '{{سرشناسی|فیلمها}}: رهنمود سرشناسی فیلمها', value: 'Films' }, // localized
{ label: '{{سرشناسی|مکان}}: رهنمود سرشناسی ویژگیهای جغرافیایی', value: 'Geographic' }, // localized
{ label: '{{سرشناسی|فهرستها}}: رهنمود سرشناسی فهرستهای مسقل', value: 'Lists' }, // localized
{ label: '{{سرشناسی|موسیقی}}: رهنمود سرشناسی موسیقی', value: 'Music' }, // localized
{ label: '{{سرشناسی|نوواژهها}}: رهنمود سرشناسی نوواژهها', value: 'Neologisms' }, // localized
{ label: '{{سرشناسی|عددها}}: رهنمود سرشناسی عددها', value: 'Numbers' }, // localized
{ label: '{{سرشناسی|محصولات}}: رهنمود سرشناسی محصولات و خدمات', value: 'Products' }, // localized
{ label: '{{سرشناسی|ورزشها}}: رهنمود سرشناسی ورزشها', value: 'Sports' }, // localized
{ label: '{{سرشناسی|تلویزیون}}: رهنمود سرشناسی برنامههای تلویزیونی', value: 'Television' }, // localized
{ label: '{{سرشناسی|وب}}: رهنمود سرشناسی مجتوای وب', value: 'Web' } // localized
]
};
break;
default:
break;
}
return checkbox;
};
var makeCheckboxesForAlreadyPresentTags = function() {
container.append({ type: 'header', id: 'tagHeader0', label: 'Tags already present' });
var subdiv = container.append({ type: 'div', id: 'tagSubdiv0' });
var checkboxes = [];
var unCheckedTags = e.target.form.getUnchecked('alreadyPresentArticleTags') || [];
Twinkle.tag.alreadyPresentTags.forEach(function(tag) {
var description = Twinkle.tag.article.tags[tag];
var checkbox =
{
value: tag,
label: '{{' + tag + '}}' + (description ? ': ' + description : ''),
checked: unCheckedTags.indexOf(tag) === -1,
style: 'font-style: italic'
};
checkboxes.push(checkbox);
});
subdiv.append({
type: 'checkbox',
name: 'alreadyPresentArticleTags',
list: checkboxes
});
};
if (sortorder === 'cat') { // categorical sort order
// function to iterate through the tags and create a checkbox for each one
var doCategoryCheckboxes = function(subdiv, array) {
var checkboxes = [];
$.each(array, function(k, tag) {
var description = Twinkle.tag.article.tags[tag];
if (Twinkle.tag.alreadyPresentTags.indexOf(tag) === -1) {
checkboxes.push(makeCheckbox(tag, description));
}
});
subdiv.append({
type: 'checkbox',
name: 'articleTags',
list: checkboxes
});
};
if (Twinkle.tag.alreadyPresentTags.length > 0) {
makeCheckboxesForAlreadyPresentTags();
}
var i = 1;
// go through each category and sub-category and append lists of checkboxes
$.each(Twinkle.tag.article.tagCategories, function(title, content) {
container.append({ type: 'header', id: 'tagHeader' + i, label: title }); // localized
var subdiv = container.append({ type: 'div', id: 'tagSubdiv' + i++ });
if (Array.isArray(content)) {
doCategoryCheckboxes(subdiv, content);
} else {
$.each(content, function(subtitle, subcontent) {
subdiv.append({ type: 'div', label: [ Morebits.htmlNode('b', subtitle) ] });
doCategoryCheckboxes(subdiv, subcontent);
});
}
});
} else { // alphabetical sort order
if (Twinkle.tag.alreadyPresentTags.length > 0) {
makeCheckboxesForAlreadyPresentTags();
container.append({ type: 'header', id: 'tagHeader1', label: 'برچسبهای قابل استفاده' }); // localized
}
var checkboxes = [];
$.each(Twinkle.tag.article.tags, function(tag, description) {
if (Twinkle.tag.alreadyPresentTags.indexOf(tag) === -1) {
checkboxes.push(makeCheckbox(tag, description));
}
});
container.append({
type: 'checkbox',
name: 'articleTags',
list: checkboxes
});
}
// append any custom tags
if (Twinkle.getPref('customTagList').length) {
container.append({ type: 'header', label: 'برچسبهای سفارشی' }); // localized
container.append({ type: 'checkbox', name: 'articleTags',
list: Twinkle.getPref('customTagList').map(function(el) {
el.checked = Twinkle.tag.checkedTags.indexOf(el.value) !== -1;
return el;
})
});
}
var $workarea = $(form).find('#tagWorkArea');
var rendered = container.render();
$workarea.empty().append(rendered);
// for quick filter:
$allCheckboxDivs = $workarea.find('[name$=Tags]').parent();
$allHeaders = $workarea.find('h5, .quickformDescription');
form.quickfilter.value = ''; // clear search, because the search results are not preserved over mode change
form.quickfilter.focus();
// style adjustments
$workarea.find('h5').css({ 'font-size': '110%' });
$workarea.find('h5:not(:first-child)').css({ 'margin-top': '1em' });
$workarea.find('div').filter(':has(span.quickformDescription)').css({ 'margin-top': '0.4em' });
var alreadyPresentTags = Morebits.quickForm.getElements(form, 'alreadyPresentArticleTags');
if (alreadyPresentTags) {
alreadyPresentTags.forEach(generateLinks);
}
// in the unlikely case that *every* tag is already on the page
var notPresentTags = Morebits.quickForm.getElements(form, 'articleTags');
if (notPresentTags) {
notPresentTags.forEach(generateLinks);
}
// tally tags added/removed, update statusNode text
var statusNode = document.getElementById('tw-tag-status');
$('[name=articleTags], [name=alreadyPresentArticleTags]').click(function() {
if (this.name === 'articleTags') {
Twinkle.tag.status.numAdded += this.checked ? 1 : -1;
} else if (this.name === 'alreadyPresentArticleTags') {
Twinkle.tag.status.numRemoved += this.checked ? -1 : 1;
}
var firstPart = 'Adding ' + Twinkle.tag.status.numAdded + ' tag' + (Twinkle.tag.status.numAdded > 1 ? 's' : '');
var secondPart = 'Removing ' + Twinkle.tag.status.numRemoved + ' tag' + (Twinkle.tag.status.numRemoved > 1 ? 's' : '');
statusNode.textContent =
(Twinkle.tag.status.numAdded ? ' ' + firstPart : '') +
(Twinkle.tag.status.numRemoved ? (Twinkle.tag.status.numAdded ? '; ' : ' ') + secondPart : '');
});
};
/**
* Adds a link to each template's description page
* @param {Morebits.quickForm.element} checkbox associated with the template
*/
var generateLinks = function(checkbox) {
var link = Morebits.htmlNode('a', '>');
link.setAttribute('class', 'tag-template-link');
var tagname = checkbox.values;
link.setAttribute('href', mw.util.getUrl(
(tagname.indexOf(':') === -1 ? 'Template:' : '') +
(tagname.indexOf('|') === -1 ? tagname : tagname.slice(0, tagname.indexOf('|')))
));
link.setAttribute('target', '_blank');
$(checkbox).parent().append(['\u00A0', link]);
};
// Tags for ARTICLES start here
Twinkle.tag.article = {};
// A list of all article tags, in alphabetical order
// To ensure tags appear in the default "categorized" view, add them to the tagCategories hash below.
Twinkle.tag.article.tags = {
'ویکیسازی': 'نیازمند ویکیسازی',
'منبع': 'نیازمند منبع',
'ابهامزدایی': 'ابهامزدایی'
};
// A list of tags in order of category
// Tags should be in alphabetical order within the categories
// Add new categories with discretion - the list is long enough as is!
Twinkle.tag.article.tagCategories = {
'برچسبهای نگهداری': [
'ویکیسازی',
'منبع',
'ابهامزدایی'
]
};
// Contains those article tags that *do not* work inside {{multiple issues}}.
Twinkle.tag.multipleIssuesExceptions = [
'Copypaste',
'Current', // Works but not intended for use in MI
'Expand language',
'GOCEinuse',
'History merge',
'Improve categories',
'In use',
'Merge',
'Merge from',
'Merge to',
'Not English',
'Rough translation',
'Uncategorized',
'Under construction'
];
// Tags for REDIRECTS start here
Twinkle.tag.spellingList = [];
Twinkle.tag.alternativeList = [];
Twinkle.tag.administrativeList = [];
// maintenance tags for FILES start here
Twinkle.tag.file = {};
Twinkle.tag.file.licenseList = [];
Twinkle.tag.file.commonsList = [];
Twinkle.tag.file.cleanupList = [];
Twinkle.tag.file.qualityList = [];
Twinkle.tag.file.replacementList = [];
Twinkle.tag.callbacks = {
article: function articleCallback(pageobj) {
// Remove tags that become superfluous with this action
var pageText = pageobj.getPageText().replace(/\{\{\s*([Uu]serspace draft)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/g, '');
var params = pageobj.getCallbackParameters();
/**
* Saves the page following the removal of tags if any. The last step.
* Called from removeTags()
*/
var postRemoval = function() {
if (params.tagsToRemove.length) {
// Remove empty {{multiple issues}} if found
pageText = pageText.replace(/\{\{(multiple ?issues|article ?issues|mi)\s*\|\s*\}\}\n?/im, '');
// Remove single-element {{multiple issues}} if found
pageText = pageText.replace(/\{\{(?:multiple ?issues|article ?issues|mi)\s*\|\s*(\{\{[^}]+\}\})\s*\}\}/im, '$1');
}
// Build edit summary
var makeSentence = function(array) {
if (array.length < 3) {
return array.join(' and ');
}
var last = array.pop();
return array.join(', ') + ', and ' + last;
};
var makeTemplateLink = function(tag) {
var text = '{{[[';
// if it is a custom tag with a parameter
if (tag.indexOf('|') !== -1) {
tag = tag.slice(0, tag.indexOf('|'));
}
text += tag.indexOf(':') !== -1 ? tag : 'Template:' + tag + '|' + tag;
return text + ']]}}';
};
var summaryText;
var addedTags = params.tags.map(makeTemplateLink);
var removedTags = params.tagsToRemove.map(makeTemplateLink);
if (addedTags.length) {
summaryText = 'Added ' + makeSentence(addedTags);
summaryText += removedTags.length ? '; and removed ' + makeSentence(removedTags) : '';
} else {
summaryText = 'Removed ' + makeSentence(removedTags);
}
summaryText += ' tag' + (addedTags.length + removedTags.length > 1 ? 's' : '');
if (params.reason) {
summaryText += ': ' + params.reason;
}
// avoid truncated summaries
if (summaryText.length > (499 - Twinkle.getPref('summaryAd').length)) {
summaryText = summaryText.replace(/\[\[[^|]+\|([^\]]+)\]\]/g, '$1');
}
pageobj.setPageText(pageText);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages'));
pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor'));
pageobj.setCreateOption('nocreate');
pageobj.save(function() {
// special functions for merge tags
if (params.mergeReason) {
// post the rationale on the talk page (only operates in main namespace)
var talkpageText = '\n\n== ' + params.talkDiscussionTitleLinked + ' ==\n\n';
talkpageText += params.mergeReason.trim() + ' ~~~~';
var talkpage = new Morebits.wiki.page('Talk:' + params.discussArticle, 'Posting rationale on talk page');
talkpage.setAppendText(talkpageText);
talkpage.setEditSummary('/* ' + params.talkDiscussionTitle + ' */ new section' + Twinkle.getPref('summaryAd'));
talkpage.setWatchlist(Twinkle.getPref('watchMergeDiscussions'));
talkpage.setCreateOption('recreate');
talkpage.append();
}
if (params.mergeTagOther) {
// tag the target page if requested
var otherTagName = 'Merge';
if (params.mergeTag === 'Merge from') {
otherTagName = 'Merge to';
} else if (params.mergeTag === 'Merge to') {
otherTagName = 'Merge from';
}
var newParams = {
tags: [otherTagName],
tagsToRemove: [],
tagsToRemain: [],
mergeTarget: Morebits.pageNameNorm,
discussArticle: params.discussArticle,
talkDiscussionTitle: params.talkDiscussionTitle,
talkDiscussionTitleLinked: params.talkDiscussionTitleLinked
};
var otherpage = new Morebits.wiki.page(params.mergeTarget, 'Tagging other page (' +
params.mergeTarget + ')');
otherpage.setCallbackParameters(newParams);
otherpage.load(Twinkle.tag.callbacks.article);
}
// post at WP:PNT for {{not English}} and {{rough translation}} tag
if (params.translationPostAtPNT) {
var pntPage = new Morebits.wiki.page('Wikipedia:Pages needing translation into English',
'Listing article at Wikipedia:Pages needing translation into English');
pntPage.setFollowRedirect(true);
pntPage.load(function friendlytagCallbacksTranslationListPage(pageobj) {
var old_text = pageobj.getPageText();
var template = params.tags.indexOf('Rough translation') !== -1 ? 'duflu' : 'needtrans';
var lang = params.translationLanguage;
var reason = params.translationComments;
var templateText = '{{subst:' + template + '|pg=' + Morebits.pageNameNorm + '|Language=' +
(lang || 'uncertain') + '|Comments=' + reason.trim() + '}} ~~~~';
var text, summary;
if (template === 'duflu') {
text = old_text + '\n\n' + templateText;
summary = 'Translation cleanup requested on ';
} else {
text = old_text.replace(/\n+(==\s?Translated pages that could still use some cleanup\s?==)/,
'\n\n' + templateText + '\n\n$1');
summary = 'Translation' + (lang ? ' from ' + lang : '') + ' requested on ';
}
if (text === old_text) {
pageobj.getStatusElement().error('failed to find target spot for the discussion');
return;
}
pageobj.setPageText(text);
pageobj.setEditSummary(summary + ' [[:' + Morebits.pageNameNorm + ']]' + Twinkle.getPref('summaryAd'));
pageobj.setCreateOption('recreate');
pageobj.save();
});
}
if (params.translationNotify) {
pageobj.lookupCreation(function(innerPageobj) {
var initialContrib = innerPageobj.getCreator();
// Disallow warning yourself
if (initialContrib === mw.config.get('wgUserName')) {
innerPageobj.getStatusElement().warn('You (' + initialContrib + ') created this page; skipping user notification');
return;
}
var userTalkPage = new Morebits.wiki.page('User talk:' + initialContrib,
'Notifying initial contributor (' + initialContrib + ')');
var notifytext = '\n\n== Your article [[' + Morebits.pageNameNorm + ']]==\n' +
'{{subst:uw-notenglish|1=' + Morebits.pageNameNorm +
(params.translationPostAtPNT ? '' : '|nopnt=yes') + '}} ~~~~';
userTalkPage.setAppendText(notifytext);
userTalkPage.setEditSummary('Notice: Please use English when contributing to the English Wikipedia.' +
Twinkle.getPref('summaryAd'));
userTalkPage.setCreateOption('recreate');
userTalkPage.setFollowRedirect(true);
userTalkPage.append();
});
}
});
if (params.patrol) {
pageobj.triage();
}
};
/**
* Removes the existing tags that were deselected (if any)
* Calls postRemoval() when done
*/
var removeTags = function removeTags() {
if (params.tagsToRemove.length === 0) {
postRemoval();
return;
}
Morebits.status.info('Info', 'Removing deselected tags that were already present');
var getRedirectsFor = [];
// Remove the tags from the page text, if found in its proper name,
// otherwise moves it to `getRedirectsFor` array earmarking it for
// later removal
params.tagsToRemove.forEach(function removeTag(tag) {
var tag_re = new RegExp('\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]+)?\\}\\}\\n?');
if (tag_re.test(pageText)) {
pageText = pageText.replace(tag_re, '');
} else {
getRedirectsFor.push('Template:' + tag);
}
});
if (!getRedirectsFor.length) {
postRemoval();
return;
}
// Remove tags which appear in page text as redirects
var api = new Morebits.wiki.api('Getting template redirects', {
'action': 'query',
'prop': 'linkshere',
'titles': getRedirectsFor.join('|'),
'redirects': 1, // follow redirect if the class name turns out to be a redirect page
'lhnamespace': '10', // template namespace only
'lhshow': 'redirect',
'lhlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
}, function removeRedirectTag(apiobj) {
$(apiobj.responseXML).find('page').each(function(idx, page) {
var removed = false;
$(page).find('lh').each(function(idx, el) {
var tag = $(el).attr('title').slice(9);
var tag_re = new RegExp('\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]*)?\\}\\}\\n?');
if (tag_re.test(pageText)) {
pageText = pageText.replace(tag_re, '');
removed = true;
return false; // break out of $.each
}
});
if (!removed) {
Morebits.status.warn('Info', 'Failed to find {{' +
$(page).attr('title').slice(9) + '}} on the page... excluding');
}
});
postRemoval();
});
api.post();
};
if (!params.tags.length) {
removeTags();
return;
}
var tagRe, tagText = '', tags = [], groupableTags = [], groupableExistingTags = [];
// Executes first: addition of selected tags
/**
* Updates `tagText` with the syntax of `tagName` template with its parameters
* @param {number} tagIndex
* @param {string} tagName
*/
var addTag = function articleAddTag(tagIndex, tagName) {
var currentTag = '';
if (tagName === 'Uncategorized' || tagName === 'Improve categories') {
pageText += '\n\n{{' + tagName + '|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}';
} else {
currentTag += '{{' + tagName;
// fill in other parameters, based on the tag
switch (tagName) {
case 'Cleanup':
currentTag += '|reason=' + params.cleanup;
break;
case 'Close paraphrasing':
currentTag += '|source=' + params.closeParaphrasing;
break;
case 'Copy edit':
if (params.copyEdit) {
currentTag += '|for=' + params.copyEdit;
}
break;
case 'Copypaste':
if (params.copypaste) {
currentTag += '|url=' + params.copypaste;
}
break;
case 'Expand language':
currentTag += '|topic=';
currentTag += '|langcode=' + params.expandLanguageLangCode;
if (params.expandLanguageArticle !== null) {
currentTag += '|otherarticle=' + params.expandLanguageArticle;
}
break;
case 'Expert needed':
if (params.expertNeeded) {
currentTag += '|1=' + params.expertNeeded;
}
if (params.expertNeededTalk) {
currentTag += '|talk=' + params.expertNeededTalk;
}
if (params.expertNeededReason) {
currentTag += '|reason=' + params.expertNeededReason;
}
break;
case 'Globalize':
currentTag += '|1=article';
if (params.globalizeRegion) {
currentTag += '|2=' + params.globalizeRegion;
}
break;
case 'News release':
currentTag += '|1=article';
break;
case 'Notability':
if (params.notability !== 'none') {
currentTag += '|' + params.notability;
}
break;
case 'Not English':
case 'Rough translation':
if (params.translationLanguage) {
currentTag += '|1=' + params.translationLanguage;
}
if (params.translationPostAtPNT) {
currentTag += '|listed=yes';
}
break;
case 'History merge':
currentTag += '|originalpage=' + params.histmergeOriginalPage;
if (params.histmergeReason) {
currentTag += '|reason=' + params.histmergeReason;
}
if (params.histmergeSysopDetails) {
currentTag += '|details=' + params.histmergeSysopDetails;
}
break;
case 'Merge':
case 'Merge to':
case 'Merge from':
params.mergeTag = tagName;
// normalize the merge target for now and later
params.mergeTarget = Morebits.string.toUpperCaseFirstChar(params.mergeTarget.replace(/_/g, ' '));
currentTag += '|' + params.mergeTarget;
// link to the correct section on the talk page, for article space only
if (mw.config.get('wgNamespaceNumber') === 0 && (params.mergeReason || params.discussArticle)) {
if (!params.discussArticle) {
// discussArticle is the article whose talk page will contain the discussion
params.discussArticle = tagName === 'Merge to' ? params.mergeTarget : mw.config.get('wgTitle');
// nonDiscussArticle is the article which won't have the discussion
params.nonDiscussArticle = tagName === 'Merge to' ? mw.config.get('wgTitle') : params.mergeTarget;
var direction = '[[' + params.nonDiscussArticle + ']]' + (params.mergeTag === 'Merge' ? ' with ' : ' into ') + '[[' + params.discussArticle + ']]';
params.talkDiscussionTitleLinked = 'Proposed merge of ' + direction;
params.talkDiscussionTitle = params.talkDiscussionTitleLinked.replace(/\[\[(.*?)\]\]/g, '$1');
}
currentTag += '|discuss=Talk:' + params.discussArticle + '#' + params.talkDiscussionTitle;
}
break;
default:
break;
}
currentTag += '|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}\n';
tagText += currentTag;
}
};
/**
* Adds the tags which go outside {{multiple issues}}, either because
* these tags aren't supported in {{multiple issues}} or because
* {{multiple issues}} is not being added to the page at all
*/
var addUngroupedTags = function() {
$.each(tags, addTag);
// Smartly insert the new tags after any hatnotes or
// afd, csd, or prod templates or hatnotes. Regex is
// extra complicated to allow for templates with
// parameters and to handle whitespace properly.
pageText = pageText.replace(
new RegExp(
// leading whitespace
'^\\s*' +
// capture template(s)
'(?:((?:\\s*' +
// AfD is special, as the tag includes html comments before and after the actual template
'(?:<!--.*AfD.*\\n\\{\\{(?:Article for deletion\\/dated|AfDM).*\\}\\}\\n<!--.*(?:\\n<!--.*)?AfD.*(?:\\s*\\n))?|' + // trailing whitespace/newline needed since this subst's a newline
// begin template format
'\\{\\{\\s*(?:' +
// CSD
'db|delete|db-.*?|speedy deletion-.*?|' +
// PROD
'(?:proposed deletion|prod blp)\\/dated(?:\\s*\\|(?:concern|user|timestamp|help).*)+|' +
// various hatnote templates
'about|correct title|dablink|distinguish|for|other\\s?(?:hurricaneuses|people|persons|places|uses(?:of)?)|redirect(?:-acronym)?|see\\s?(?:also|wiktionary)|selfref|short description|the' +
// not a hatnote, but sometimes under a CSD or AfD
'|salt|proposed deletion endorsed' +
// end main template name, optionally with a number (such as redirect2)
')\\d*\\s*' +
// template parameters
'(\\|(?:\\{\\{[^{}]*\\}\\}|[^{}])*)?' +
// end template format
'\\}\\})+' +
// end capture
'(?:\\s*\\n)?)' +
// trailing whitespace
'\\s*)?',
'i'), '$1' + tagText
);
removeTags();
};
// Separate tags into groupable ones (`groupableTags`) and non-groupable ones (`tags`)
params.tags.forEach(function(tag) {
tagRe = new RegExp('\\{\\{' + tag + '(\\||\\}\\})', 'im');
// regex check for preexistence of tag can be skipped if in canRemove mode
if (Twinkle.tag.canRemove || !tagRe.exec(pageText)) {
// condition Twinkle.tag.article.tags[tag] to ensure that its not a custom tag
// Custom tags are assumed non-groupable, since we don't know whether MI template supports them
if (Twinkle.tag.article.tags[tag] && Twinkle.tag.multipleIssuesExceptions.indexOf(tag) === -1) {
groupableTags.push(tag);
} else {
tags.push(tag);
}
} else {
if (tag === 'Merge from' || tag === 'History merge') {
tags.push(tag);
} else {
Morebits.status.warn('Info', 'Found {{' + tag +
'}} on the article already...excluding');
// don't do anything else with merge tags
if (['Merge', 'Merge to'].indexOf(tag) !== -1) {
params.mergeTarget = params.mergeReason = params.mergeTagOther = null;
}
}
}
});
// To-be-retained existing tags that are groupable
params.tagsToRemain.forEach(function(tag) {
if (Twinkle.tag.multipleIssuesExceptions.indexOf(tag) === -1) {
groupableExistingTags.push(tag);
}
});
var miTest = /\{\{(multiple ?issues|article ?issues|mi)(?!\s*\|\s*section\s*=)[^}]+\{/im.exec(pageText);
if (miTest && groupableTags.length > 0) {
Morebits.status.info('Info', 'Adding supported tags inside existing {{multiple issues}} tag');
tagText = '';
$.each(groupableTags, addTag);
var miRegex = new RegExp('(\\{\\{\\s*' + miTest[1] + '\\s*(?:\\|(?:\\{\\{[^{}]*\\}\\}|[^{}])*)?)\\}\\}\\s*', 'im');
pageText = pageText.replace(miRegex, '$1' + tagText + '}}\n');
tagText = '';
addUngroupedTags();
} else if (params.group && !miTest && (groupableExistingTags.length + groupableTags.length) >= 2) {
Morebits.status.info('Info', 'Grouping supported tags inside {{multiple issues}}');
tagText += '{{Multiple issues|\n';
/**
* Adds newly added tags to MI
*/
var addNewTagsToMI = function() {
$.each(groupableTags, addTag);
tagText += '}}\n';
addUngroupedTags();
};
var getRedirectsFor = [];
// Reposition the tags on the page into {{multiple issues}}, if found with its
// proper name, else moves it to `getRedirectsFor` array to be handled later
groupableExistingTags.forEach(function repositionTagIntoMI(tag) {
var tag_re = new RegExp('(\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]+)?\\}\\}\\n?)');
if (tag_re.test(pageText)) {
tagText += tag_re.exec(pageText)[1];
pageText = pageText.replace(tag_re, '');
} else {
getRedirectsFor.push('Template:' + tag);
}
});
if (!getRedirectsFor.length) {
addNewTagsToMI();
return;
}
var api = new Morebits.wiki.api('Getting template redirects', {
'action': 'query',
'prop': 'linkshere',
'titles': getRedirectsFor.join('|'),
'redirects': 1,
'lhnamespace': '10', // template namespace only
'lhshow': 'redirect',
'lhlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
}, function replaceRedirectTag(apiobj) {
$(apiobj.responseXML).find('page').each(function(idx, page) {
var found = false;
$(page).find('lh').each(function(idx, el) {
var tag = $(el).attr('title').slice(9);
var tag_re = new RegExp('(\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]*)?\\}\\}\\n?)');
if (tag_re.test(pageText)) {
tagText += tag_re.exec(pageText)[1];
pageText = pageText.replace(tag_re, '');
found = true;
return false; // break out of $.each
}
});
if (!found) {
Morebits.status.warn('Info', 'Failed to find the existing {{' +
$(page).attr('title').slice(9) + '}} on the page... skip repositioning');
}
});
addNewTagsToMI();
});
api.post();
} else {
tags = tags.concat(groupableTags);
addUngroupedTags();
}
},
redirect: function redirect(pageobj) {
var params = pageobj.getCallbackParameters(),
pageText = pageobj.getPageText(),
tagRe, tagText = '', summaryText = 'Added',
tags = [], i;
for (i = 0; i < params.tags.length; i++) {
tagRe = new RegExp('(\\{\\{' + params.tags[i] + '(\\||\\}\\}))', 'im');
if (!tagRe.exec(pageText)) {
tags.push(params.tags[i]);
} else {
Morebits.status.warn('Info', 'Found {{' + params.tags[i] +
'}} on the redirect already...excluding');
}
}
var addTag = function redirectAddTag(tagIndex, tagName) {
tagText += '\n{{' + tagName;
if (tagName === 'R from alternative language') {
if (params.altLangFrom) {
tagText += '|from=' + params.altLangFrom;
}
if (params.altLangTo) {
tagText += '|to=' + params.altLangTo;
}
}
tagText += '}}';
if (tagIndex > 0) {
if (tagIndex === (tags.length - 1)) {
summaryText += ' and';
} else if (tagIndex < (tags.length - 1)) {
summaryText += ',';
}
}
summaryText += ' {{[[:' + (tagName.indexOf(':') !== -1 ? tagName : 'Template:' + tagName + '|' + tagName) + ']]}}';
};
tags.sort();
$.each(tags, addTag);
// Check for all Rcat shell redirects (from #433)
if (pageText.match(/{{(?:redr|this is a redirect|r(?:edirect)?(?:.?cat.*)?[ _]?sh)/i)) {
// Regex inspired by [[User:Kephir/gadgets/sagittarius.js]] ([[Special:PermaLink/831402893]])
var oldTags = pageText.match(/(\s*{{[A-Za-z ]+\|)((?:[^|{}]*|{{[^}]*}})+)(}})\s*/i);
pageText = pageText.replace(oldTags[0], oldTags[1] + tagText + oldTags[2] + oldTags[3]);
} else {
// Fold any pre-existing Rcats into taglist and under Rcatshell
var pageTags = pageText.match(/\n{{R(?:edirect)? .*?}}/img);
var oldPageTags = '';
if (pageTags) {
pageTags.forEach(function(pageTag) {
var pageRe = new RegExp(pageTag, 'img');
pageText = pageText.replace(pageRe, '');
oldPageTags += pageTag;
});
}
pageText += '\n{{Redirect category shell|' + tagText + oldPageTags + '\n}}';
}
summaryText += (tags.length > 0 ? ' tag' + (tags.length > 1 ? 's' : '') : '') + ' to redirect';
// avoid truncated summaries
if (summaryText.length > (499 - Twinkle.getPref('summaryAd').length)) {
summaryText = summaryText.replace(/\[\[[^|]+\|([^\]]+)\]\]/g, '$1');
}
pageobj.setPageText(pageText);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages'));
pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor'));
pageobj.setCreateOption('nocreate');
pageobj.save();
if (params.patrol) {
pageobj.triage();
}
},
file: function friendlytagCallbacksFile(pageobj) {
var text = pageobj.getPageText();
var params = pageobj.getCallbackParameters();
var summary = 'Adding ';
// Add maintenance tags
if (params.tags.length) {
var tagtext = '', currentTag;
$.each(params.tags, function(k, tag) {
// when other commons-related tags are placed, remove "move to Commons" tag
if (['Keep local', 'subst:ncd', 'Do not move to Commons_reason', 'Do not move to Commons',
'Now Commons'].indexOf(tag) !== -1) {
text = text.replace(/\{\{(mtc|(copy |move )?to ?commons|move to wikimedia commons|copy to wikimedia commons)[^}]*\}\}/gi, '');
}
currentTag = '{{' + (tag === 'Do not move to Commons_reason' ? 'Do not move to Commons' : tag);
switch (tag) {
case 'subst:ncd':
if (params.ncdName !== '') {
currentTag += '|1=' + params.ncdName;
}
break;
case 'Keep local':
if (params.keeplocalName !== '') {
currentTag += '|1=' + params.keeplocalName;
}
break;
case 'Rename media':
if (params.renamemediaNewname !== '') {
currentTag += '|1=' + params.renamemediaNewname;
}
if (params.renamemediaReason !== '') {
currentTag += '|2=' + params.renamemediaReason;
}
break;
case 'Cleanup image':
currentTag += '|1=' + params.cleanupimageReason;
break;
case 'Image-Poor-Quality':
currentTag += '|1=' + params.ImagePoorQualityReason;
break;
case 'Low quality chem':
currentTag += '|1=' + params.lowQualityChemReason;
break;
case 'Vector version available':
text = text.replace(/\{\{((convert to |convertto|should be |shouldbe|to)?svg|badpng|vectorize)[^}]*\}\}/gi, '');
/* falls through */
case 'PNG version available':
/* falls through */
case 'Obsolete':
currentTag += '|1=' + params[tag.replace(/ /g, '_') + 'File'];
break;
case 'Do not move to Commons_reason':
currentTag += '|reason=' + params.DoNotMoveToCommons;
break;
case 'subst:orfurrev':
// remove {{non-free reduce}} and redirects
text = text.replace(/\{\{\s*(Template\s*:\s*)?(Non-free reduce|FairUseReduce|Fairusereduce|Fair Use Reduce|Fair use reduce|Reduce size|Reduce|Fair-use reduce|Image-toobig|Comic-ovrsize-img|Non-free-reduce|Nfr|Smaller image|Nonfree reduce)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/ig, '');
currentTag += '|date={{subst:date}}';
break;
case 'Copy to Commons':
currentTag += '|human=' + mw.config.get('wgUserName');
break;
case 'Should be SVG':
currentTag += '|' + params.svgCategory;
break;
default:
break; // don't care
}
currentTag += '}}\n';
tagtext += currentTag;
summary += '{{' + tag + '}}, ';
});
if (!tagtext) {
pageobj.getStatusElement().warn('User canceled operation; nothing to do');
return;
}
text = tagtext + text;
}
pageobj.setPageText(text);
pageobj.setEditSummary(summary.substring(0, summary.length - 2) + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages'));
pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor'));
pageobj.setCreateOption('nocreate');
pageobj.save();
if (params.patrol) {
pageobj.triage();
}
}
};
Twinkle.tag.callback.evaluate = function friendlytagCallbackEvaluate(e) {
var form = e.target;
var params = {};
if (form.patrolPage) {
params.patrol = form.patrolPage.checked;
}
// Don't return null if there aren't any available tags
params.tags = form.getChecked(Twinkle.tag.mode + 'Tags') || [];
// Save values of input fields into params object. This works as quickform input
// fields within subgroups of elements with name 'articleTags' (say) have their
// name attribute as 'articleTags.' + name of the subgroup element
var name_prefix = Twinkle.tag.mode + 'Tags.';
$(form).find("[name^='" + name_prefix + "']:not(div)").each(function(idx, el) {
// el are the HTMLInputElements, el.name gives the name attribute
params[el.name.slice(name_prefix.length)] =
el.type === 'checkbox' ? form[el.name].checked : form[el.name].value;
});
switch (Twinkle.tag.mode) {
case 'article':
params.tagsToRemove = form.getUnchecked('alreadyPresentArticleTags') || [];
params.tagsToRemain = form.getChecked('alreadyPresentArticleTags') || [];
params.reason = form.reason.value.trim();
params.group = form.group.checked;
// Validation
if ((params.tags.indexOf('Merge') !== -1) || (params.tags.indexOf('Merge from') !== -1) ||
(params.tags.indexOf('Merge to') !== -1)) {
if (((params.tags.indexOf('Merge') !== -1) + (params.tags.indexOf('Merge from') !== -1) +
(params.tags.indexOf('Merge to') !== -1)) > 1) {
alert('Please select only one of {{merge}}, {{merge from}}, and {{merge to}}. If several merges are required, use {{merge}} and separate the article names with pipes (although in this case Twinkle cannot tag the other articles automatically).');
return;
}
if (!params.mergeTarget) {
alert('Please specify the title of the other article for use in the merge template.');
return;
}
if ((params.mergeTagOther || params.mergeReason) && params.mergeTarget.indexOf('|') !== -1) {
alert('Tagging multiple articles in a merge, and starting a discussion for multiple articles, is not supported at the moment. Please turn off "tag other article", and/or clear out the "reason" box, and try again.');
return;
}
}
if ((params.tags.indexOf('Not English') !== -1) && (params.tags.indexOf('Rough translation') !== -1)) {
alert('Please select only one of {{not English}} and {{rough translation}}.');
return;
}
if (params.tags.indexOf('History merge') !== -1 && params.histmergeOriginalPage.trim() === '') {
alert('You must specify a page to be merged for the {{history merge}} tag.');
return;
}
if (params.tags.indexOf('Cleanup') !== -1 && params.cleanup.trim() === '') {
alert('You must specify a reason for the {{cleanup}} tag.');
return;
}
if (params.tags.indexOf('Expand language') !== -1 && params.expandLanguageLangCode.trim() === '') {
alert('You must specify language code for the {{expand language}} tag.');
return;
}
break;
case 'file':
if (params.tags.indexOf('Cleanup image') !== -1 && params.cleanupimageReason === '') {
alert('You must specify a reason for the cleanup tag.');
return;
}
if (params.tags.indexOf('Image-Poor-Quality') !== -1 && params.ImagePoorQualityReason === '') {
alert('You must specify a reason for the {{Image-Poor-Quality}} tag');
return;
}
if (params.tags.indexOf('Low Quality Chem') !== -1 && params.lowQualityChemReason === '') {
alert('You must specify a reason for the {{Low Quality Chem}} tag');
return;
}
if ((params.tags.indexOf('Obsolete') !== -1 && params.ObsoleteFile === '') ||
(params.tags.indexOf('PNG version available') !== -1 && params.PNG_version_availableFile === '') ||
(params.tags.indexOf('Vector version available') !== -1 && params.Vector_version_availableFile === '')
) {
alert('You must specify the replacement file name for a tag in the Replacement tags list');
return;
}
if (params.tags.indexOf('Do not move to Commons_reason') !== -1 && params.DoNotMoveToCommons === '') {
alert('You must specify a reason for the {{Do not move to Commons}} tag');
return;
}
break;
case 'redirect':
break;
default:
alert('Twinkle.tag: unknown mode ' + Twinkle.tag.mode);
break;
}
// File/redirect: return if no tags selected
// Article: return if no tag is selected and no already present tag is deselected
if (params.tags.length === 0 && (Twinkle.tag.mode !== 'article' || params.tagsToRemove.length === 0)) {
alert('You must select at least one tag!');
return;
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = Morebits.pageNameNorm;
Morebits.wiki.actionCompleted.notice = 'Tagging complete, reloading article in a few seconds';
if (Twinkle.tag.mode === 'redirect') {
Morebits.wiki.actionCompleted.followRedirect = false;
}
var wikipedia_page = new Morebits.wiki.page(Morebits.pageNameNorm, 'Tagging ' + Twinkle.tag.mode);
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.tag.callbacks[Twinkle.tag.mode]);
};
})(jQuery);
// </nowiki>
/* ===== twinklediff.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklediff.js: Diff module
****************************************
* Mode of invocation: Tab on non-diff pages ("Last"); tabs on diff pages ("Since", "Since mine", "Current")
* Active on: Existing non-special pages
*/
Twinkle.diff = function twinklediff() {
if (mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId')) {
return;
}
Twinkle.addPortletLink(mw.util.getUrl(mw.config.get('wgPageName'), {diff: 'cur', oldid: 'prev'}), 'آخرین ویرایش', 'tw-lastdiff', 'نمایش تفاوت در آخرین ویرایش'); // localized
// Show additional tabs only on diff pages
if (mw.util.getParamValue('diff')) {
Twinkle.addPortletLink(function() {
Twinkle.diff.evaluate(false);
}, 'تفاوت از نفر قبلی', 'tw-since', 'نمایش تفاوت بین آخرین تفاوت به ویرایش کاربر قبل از آن'); // localized
Twinkle.addPortletLink(function() {
Twinkle.diff.evaluate(true);
}, 'تفاوت از ویرایش من', 'tw-sincemine', 'نمایش تفاوت بین آخرین تفاوت و آخرین ویرایش من'); // localized
var oldid = /oldid=(.+)/.exec($('#mw-diff-ntitle1').find('strong a').first().attr('href'))[1];
Twinkle.addPortletLink(mw.util.getUrl(mw.config.get('wgPageName'), {diff: 'cur', oldid: oldid}), 'تفاوت با نسخهٔ فعلی', 'tw-curdiff', 'نمایش تفاوت با نسخهٔ فعلی'); // localized
}
};
Twinkle.diff.evaluate = function twinklediffEvaluate(me) {
var user;
if (me) {
user = mw.config.get('wgUserName');
} else {
var node = document.getElementById('mw-diff-ntitle2');
if (!node) {
// nothing to do?
return;
}
user = $(node).find('a').first().text();
}
var query = {
'prop': 'revisions',
'action': 'query',
'titles': mw.config.get('wgPageName'),
'rvlimit': 1,
'rvprop': [ 'ids', 'user' ],
'rvstartid': mw.config.get('wgCurRevisionId') - 1, // i.e. not the current one
'rvuser': user
};
Morebits.status.init(document.getElementById('mw-content-text'));
var wikipedia_api = new Morebits.wiki.api('گرفتن اطلاعات مشارکتکنندهٔ اولیه', query, Twinkle.diff.callbacks.main); // localized
wikipedia_api.params = { user: user };
wikipedia_api.post();
};
Twinkle.diff.callbacks = {
main: function(self) {
var xmlDoc = self.responseXML;
var revid = $(xmlDoc).find('rev').attr('revid');
if (!revid) {
self.statelem.error('هیچ نسخهٔ قبلیتر مناسبی پیدا نشد، یا ' + self.params.user + 'تنها مشارکتکننده بودهاست. پروسه متوقف شد.'); // localized
return;
}
window.location = mw.util.getUrl(mw.config.get('wgPageName'), {
diff: mw.config.get('wgCurRevisionId'),
oldid: revid
});
}
};
})(jQuery);
// </nowiki>
/* ===== twinkleunlink.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinkleunlink.js: Unlink module
****************************************
* Mode of invocation: Tab ("Unlink")
* Active on: Non-special pages, except Wikipedia:Sandbox
*/
Twinkle.unlink = function twinkleunlink() {
if (mw.config.get('wgNamespaceNumber') < 0 || mw.config.get('wgPageName') === 'ویکیواژه:صفحه تمرین' || // localized
// Restrict to extended confirmed users (see #428)
(!Morebits.userIsInGroup('extendedconfirmed') && !Morebits.userIsSysop)) {
return;
}
Twinkle.addPortletLink(Twinkle.unlink.callback, 'پیوندزدایی', 'tw-unlink', 'زدودن پیوندهای درونویکی'); // localized
};
Twinkle.unlink.getChecked2 = function twinkleunlinkGetChecked2(nodelist) {
if (!(nodelist instanceof NodeList) && !(nodelist instanceof HTMLCollection)) {
return nodelist.checked ? [ nodelist.values ] : [];
}
var result = [];
for (var i = 0; i < nodelist.length; ++i) {
if (nodelist[i].checked) {
result.push(nodelist[i].values);
}
}
return result;
};
// the parameter is used when invoking unlink from admin speedy
Twinkle.unlink.callback = function(presetReason) {
var Window = new Morebits.simpleWindow(600, 440);
Window.setTitle('زدودن پیوندها' + (mw.config.get('wgNamespaceNumber') === 6 ? ' و کاربردهای پرونده' : '')); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#unlink'); // localized
var form = new Morebits.quickForm(Twinkle.unlink.callback.evaluate);
// prepend some basic documentation
var node1 = Morebits.htmlNode('code', '[[' + Morebits.pageNameNorm + '|متن پیوند]]'); // localized
var node2 = Morebits.htmlNode('code', 'متن پیوند'); // localized
node1.style.fontFamily = node2.style.fontFamily = 'monospace';
node1.style.fontStyle = node2.style.fontStyle = 'normal';
form.append({
type: 'div',
style: 'margin-bottom: 0.5em',
label: [
'این ابزار اجازه میدهد که پیوندهای ورودی به این صفحه را از صفحههای دیگر بزدایید' + // localized
(mw.config.get('wgNamespaceNumber') === 6 ? '، و/یا کاربردهای این پرونده در صفحههای دیگر را با دستور <!-- --> پنهان کنید' : '') + // localized
'. برای مثال، ', // localized
node1,
' تبدیل میشود به ', // localized
node2,
'. از این ابزار با احتیاط استفاده کنید.' // localized
]
});
form.append({
type: 'input',
name: 'reason',
label: 'دلیل: ', // localized
value: presetReason ? presetReason : '',
size: 60
});
var query;
if (mw.config.get('wgNamespaceNumber') === 6) { // File:
query = {
'action': 'query',
'list': [ 'backlinks', 'imageusage' ],
'bltitle': mw.config.get('wgPageName'),
'iutitle': mw.config.get('wgPageName'),
'bllimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
'iulimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
'blnamespace': Twinkle.getPref('unlinkNamespaces'),
'iunamespace': Twinkle.getPref('unlinkNamespaces'),
'rawcontinue': true
};
} else {
query = {
'action': 'query',
'list': 'backlinks',
'bltitle': mw.config.get('wgPageName'),
'blfilterredir': 'nonredirects',
'bllimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
'blnamespace': Twinkle.getPref('unlinkNamespaces'),
'rawcontinue': true
};
}
var wikipedia_api = new Morebits.wiki.api('گرفتن پیوندها', query, Twinkle.unlink.callbacks.display.backlinks); // localized
wikipedia_api.params = { form: form, Window: Window, image: mw.config.get('wgNamespaceNumber') === 6 };
wikipedia_api.post();
var root = document.createElement('div');
root.style.padding = '15px'; // just so it doesn't look broken
Morebits.status.init(root);
wikipedia_api.statelem.status('در حال بارگیری...'); // localized
Window.setContent(root);
Window.display();
};
Twinkle.unlink.callback.evaluate = function twinkleunlinkCallbackEvaluate(event) {
var reason = event.target.reason.value;
if (!reason) {
alert('شما باید دلیل زدودن پیوندها را مشخص کنید.'); // localized
return;
}
var backlinks = [], imageusage = [];
if (event.target.backlinks) {
backlinks = Twinkle.unlink.getChecked2(event.target.backlinks);
}
if (event.target.imageusage) {
imageusage = Twinkle.unlink.getChecked2(event.target.imageusage);
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(event.target);
var pages = Morebits.array.uniq(backlinks.concat(imageusage));
var unlinker = new Morebits.batchOperation('زدودن پیوندهای ورودی' + (imageusage ? ' و موارد استفاده از پرونده' : '')); // localized
unlinker.setOption('preserveIndividualStatusLines', true);
unlinker.setPageList(pages);
var params = { reason: reason, unlinker: unlinker };
unlinker.run(function(pageName) {
var wikipedia_page = new Morebits.wiki.page(pageName, 'زدودن پیوندها در صفحهٔ «' + pageName + '»'); // localized
wikipedia_page.setBotEdit(true); // unlink considered a floody operation
var innerParams = $.extend({}, params);
innerParams.doBacklinks = backlinks && backlinks.indexOf(pageName) !== -1;
innerParams.doImageusage = imageusage && imageusage.indexOf(pageName) !== -1;
wikipedia_page.setCallbackParameters(innerParams);
wikipedia_page.load(Twinkle.unlink.callbacks.unlinkBacklinks);
});
};
Twinkle.unlink.callbacks = {
display: {
backlinks: function twinkleunlinkCallbackDisplayBacklinks(apiobj) {
var xmlDoc = apiobj.responseXML;
var havecontent = false;
var list, namespaces, i;
if (apiobj.params.image) {
var imageusage = $(xmlDoc).find('query imageusage iu');
list = [];
for (i = 0; i < imageusage.length; ++i) {
var usagetitle = imageusage[i].getAttribute('title');
list.push({ label: usagetitle, value: usagetitle, checked: true });
}
if (!list.length) {
apiobj.params.form.append({ type: 'div', label: 'هیچ موردی استفادهای از پرونده یافت نشد.' }); // localized
} else {
apiobj.params.form.append({ type: 'header', label: 'کاربردهای پرونده' }); // localized
namespaces = [];
$.each(Twinkle.getPref('unlinkNamespaces'), function(k, v) {
namespaces.push(v === '0' ? '(مقاله)' : mw.config.get('wgFormattedNamespaces')[v]); // localized
});
apiobj.params.form.append({
type: 'div',
label: 'فضای نام انتخابشده: ' + namespaces.join(', '), // localized
tooltip: 'با تنظیمات توینکل (در [[وو:توینکل تر]]) میتوانید این مورد را تغییر دهید' // localized
});
if ($(xmlDoc).find('query-continue').length) {
apiobj.params.form.append({
type: 'div',
label: 'اولین ' + mw.language.convertNumber(list.length.toString()) + ' کاربرد پرونده نشان داده شدهاست.' // localized
});
}
apiobj.params.form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'imageusage')).prop('checked', true);
}
});
apiobj.params.form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'imageusage')).prop('checked', false);
}
});
apiobj.params.form.append({
type: 'checkbox',
name: 'imageusage',
list: list
});
havecontent = true;
}
}
var backlinks = $(xmlDoc).find('query backlinks bl');
if (backlinks.length > 0) {
list = [];
for (i = 0; i < backlinks.length; ++i) {
var title = backlinks[i].getAttribute('title');
list.push({ label: title, value: title, checked: true });
}
apiobj.params.form.append({ type: 'header', label: 'پیوندهای ورودی' });
namespaces = [];
$.each(Twinkle.getPref('unlinkNamespaces'), function(k, v) {
namespaces.push(v === '0' ? '(مقاله)' : mw.config.get('wgFormattedNamespaces')[v]); // localized
});
apiobj.params.form.append({
type: 'div',
label: 'فضاهای نام انتخابشده: ' + namespaces.join(', '), // localized
tooltip: 'با تنظیمات توینکل (در [[وو:توینکل تر]]) میتوانید این مورد را تغییر دهید' // localized
});
if ($(xmlDoc).find('query-continue').length) {
apiobj.params.form.append({
type: 'div',
label: 'اولین ' + mw.language.convertNumber(list.length.toString()) + ' پیوندهای ورودی نشان داده شدهاند.' // localized
});
}
apiobj.params.form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'backlinks')).prop('checked', true);
}
});
apiobj.params.form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'backlinks')).prop('checked', false);
}
});
apiobj.params.form.append({
type: 'checkbox',
name: 'backlinks',
list: list
});
havecontent = true;
} else {
apiobj.params.form.append({ type: 'div', label: 'پیوند ورودی یافت نشد.' }); // localized
}
if (havecontent) {
apiobj.params.form.append({ type: 'submit' });
}
var result = apiobj.params.form.render();
apiobj.params.Window.setContent(result);
Morebits.checkboxShiftClickSupport($("input[name='imageusage']", result));
Morebits.checkboxShiftClickSupport($("input[name='backlinks']", result));
}
},
unlinkBacklinks: function twinkleunlinkCallbackUnlinkBacklinks(pageobj) {
var oldtext = pageobj.getPageText();
var params = pageobj.getCallbackParameters();
var wikiPage = new Morebits.wikitext.page(oldtext);
var summaryText = '', warningString = false;
var text;
// remove image usages
if (params.doImageusage) {
wikiPage.commentOutImage(mw.config.get('wgTitle'), 'پنهان شد'); // localized
text = wikiPage.getText();
// did we actually make any changes?
if (text === oldtext) {
warningString = 'کاربردی از پرونده'; // localized
} else {
summaryText = 'پنهان کردن کاربردهای پرونده'; // localized
oldtext = text;
}
}
// remove backlinks
if (params.doBacklinks) {
wikiPage.removeLink(Morebits.pageNameNorm);
text = wikiPage.getText();
// did we actually make any changes?
if (text === oldtext) {
warningString = warningString ? 'پیوند ورودی یا کاربردی از پرونده' : 'پیوند ورودی'; // localized
} else {
summaryText = (summaryText ? summaryText + ' / ' : '') + 'زدودن پیوندها به'; // localized
oldtext = text;
}
}
if (warningString) {
// nothing to do!
pageobj.getStatusElement().error("هیچ " + warningString + ' در صفحه یافت نشد.'); // localized
params.unlinker.workerFailure(pageobj);
return;
}
pageobj.setPageText(text);
pageobj.setEditSummary(summaryText + ' «' + Morebits.pageNameNorm + '»: ' + params.reason + '.' + Twinkle.getPref('summaryAd')); // localized
pageobj.setCreateOption('nocreate');
pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
}
};
})(jQuery);
// </nowiki>
/* ===== twinklefluff.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklefluff.js: Revert/rollback module
****************************************
* Mode of invocation: Links on contributions, recent changes, history, and diff pages
* Active on: Diff pages, history pages, Special:RecentChanges(Linked),
and Special:Contributions
*/
/**
Twinklefluff revert and antivandalism utility
*/
Twinkle.fluff = function twinklefluff() {
// A list of usernames, usually only bots, that vandalism revert is jumped over; that is,
// if vandalism revert was chosen on such username, then its target is on the revision before.
// This is for handling quick bots that makes edits seconds after the original edit is made.
// This only affects vandalism rollback; for good faith rollback, it will stop, indicating a bot
// has no faith, and for normal rollback, it will rollback that edit.
Twinkle.fluff.whiteList = [
// localized
];
if (mw.config.get('wgIsProbablyEditable') && mw.config.get('wgNamespaceNumber') !== 2600) { // localized [exclude Flow pages]
// Only proceed if the user can actually edit the page
// in question (ignored for contributions, see #632).
// wgIsProbablyEditable should take care of
// namespace/contentModel restrictions as well as
// explicit protections; it won't take care of
// cascading or TitleBlacklist restrictions
if (mw.config.get('wgDiffNewId') || mw.config.get('wgDiffOldId')) { // wgDiffOldId included for clarity in if else loop [[phab:T214985]]
mw.hook('wikipage.diff').add(function () { // Reload alongside the revision slider
Twinkle.fluff.addLinks.diff();
});
} else if (mw.config.get('wgAction') === 'view' && mw.config.get('wgCurRevisionId') !== mw.config.get('wgRevisionId')) {
Twinkle.fluff.addLinks.oldid();
} else if (mw.config.get('wgAction') === 'history') {
Twinkle.fluff.addLinks.history();
}
} else if (mw.config.get('wgNamespaceNumber') === -1) {
Twinkle.fluff.skipTalk = !Twinkle.getPref('openTalkPageOnAutoRevert');
Twinkle.fluff.rollbackInPlace = Twinkle.getPref('rollbackInPlace');
if (mw.config.get('wgCanonicalSpecialPageName') === 'Contributions') {
Twinkle.fluff.addLinks.contributions();
} else if (mw.config.get('wgCanonicalSpecialPageName') === 'Recentchanges' || mw.config.get('wgCanonicalSpecialPageName') === 'Recentchangeslinked') {
// Reload with recent changes updates
// structuredChangeFilters.ui.initialized is just on load
mw.hook('wikipage.content').add(function(item) {
if (item.is('div')) {
Twinkle.fluff.addLinks.recentchanges();
}
});
}
}
};
Twinkle.fluff.skipTalk = null;
Twinkle.fluff.rollbackInPlace = null;
// Consolidated construction of fluff links
Twinkle.fluff.linkBuilder = {
spanTag: function(color, content) {
var span = document.createElement('span');
span.style.color = color;
span.appendChild(document.createTextNode(content));
return span;
},
buildLink: function(color, text) {
var link = document.createElement('a');
link.appendChild(Twinkle.fluff.linkBuilder.spanTag('Black', '['));
link.appendChild(Twinkle.fluff.linkBuilder.spanTag(color, text));
link.appendChild(Twinkle.fluff.linkBuilder.spanTag('Black', ']'));
link.href = '#';
return link;
},
/**
* @param {string} vandal - Username of the editor being reverted (required)
* @param {boolean} inline - True to create two links in a span, false
* to create three links in a div (optional)
* @param {number|string} [rev=wgCurRevisionId] - Revision ID being reverted (optional)
* @param {string} [page=wgPageName] - Page being reverted (optional)
*/
rollbackLinks: function(vandal, inline, rev, page) {
var elem = inline ? 'span' : 'div';
var revNode = document.createElement(elem);
rev = parseInt(rev, 10);
if (rev) {
revNode.setAttribute('id', 'tw-revert' + rev);
} else {
revNode.setAttribute('id', 'tw-revert');
}
var normNode = document.createElement('strong');
var vandNode = document.createElement('strong');
var normLink = Twinkle.fluff.linkBuilder.buildLink('SteelBlue', 'برگردانی'); // localiezd
var vandLink = Twinkle.fluff.linkBuilder.buildLink('Red', 'خرابکاری'); // localized
$(normLink).click(function() {
Twinkle.fluff.revert('norm', vandal, rev, page);
Twinkle.fluff.disableLinks(revNode);
});
$(vandLink).click(function() {
Twinkle.fluff.revert('vand', vandal, rev, page);
Twinkle.fluff.disableLinks(revNode);
});
vandNode.appendChild(vandLink);
normNode.appendChild(normLink);
var separator = inline ? ' ' : ' || ';
if (!inline) {
var agfNode = document.createElement('strong');
var agfLink = Twinkle.fluff.linkBuilder.buildLink('DarkOliveGreen', 'برگردانی (فحن)'); // localized
$(agfLink).click(function() {
Twinkle.fluff.revert('agf', vandal, rev, page);
// Twinkle.fluff.disableLinks(revNode); // rollbackInPlace not relevant for any inline situations
});
agfNode.appendChild(agfLink);
revNode.appendChild(agfNode);
}
revNode.appendChild(document.createTextNode(separator));
revNode.appendChild(normNode);
revNode.appendChild(document.createTextNode(separator));
revNode.appendChild(vandNode);
return revNode;
},
// Build [restore this revision] links
restoreThisRevisionLink: function(revisionRef, inline) {
// If not a specific revision number, should be wgDiffNewId/wgDiffOldId/wgRevisionId
revisionRef = typeof revisionRef === 'number' ? revisionRef : mw.config.get(revisionRef);
var elem = inline ? 'span' : 'div';
var revertToRevisionNode = document.createElement(elem);
revertToRevisionNode.setAttribute('id', 'tw-revert-to-' + revisionRef);
revertToRevisionNode.style.fontWeight = 'bold';
var revertToRevisionLink = Twinkle.fluff.linkBuilder.buildLink('SaddleBrown', 'برگردانی به این نسخه'); // localized
$(revertToRevisionLink).click(function() {
Twinkle.fluff.revertToRevision(revisionRef);
});
if (inline) {
revertToRevisionNode.appendChild(document.createTextNode(' '));
}
revertToRevisionNode.appendChild(revertToRevisionLink);
return revertToRevisionNode;
}
};
Twinkle.fluff.addLinks = {
contributions: function() {
// $('sp-contributions-footer-anon-range') relies on the fmbox
// id in [[MediaWiki:Sp-contributions-footer-anon-range]] and
// is used to show rollback/vandalism links for IP ranges
if (mw.config.exists('wgRelevantUserName') || !!$('#sp-contributions-footer-anon-range')[0]) {
// Get the username these contributions are for
var username = mw.config.get('wgRelevantUserName');
if (Twinkle.getPref('showRollbackLinks').indexOf('contribs') !== -1 ||
(mw.config.get('wgUserName') !== username && Twinkle.getPref('showRollbackLinks').indexOf('others') !== -1) ||
(mw.config.get('wgUserName') === username && Twinkle.getPref('showRollbackLinks').indexOf('mine') !== -1)) {
var list = $('#mw-content-text').find('ul li:has(span.mw-uctop):has(.mw-changeslist-diff)');
list.each(function(key, current) {
// revid is also available in the href of both
// .mw-changeslist-date or .mw-changeslist-diff
var page = $(current).find('.mw-contributions-title').text();
current.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks(username, true, current.dataset.mwRevid, page));
});
}
}
},
recentchanges: function() {
if (Twinkle.getPref('showRollbackLinks').indexOf('recent') !== -1) {
// Latest and revertable (not page creations, logs, categorizations, etc.)
var list = $('.mw-changeslist .mw-changeslist-last.mw-changeslist-src-mw-edit');
// Exclude top-level header if "group changes" preference is used
// and find only individual lines or nested lines
list = list.not('.mw-rcfilters-ui-highlights-enhanced-toplevel').find('.mw-changeslist-line-inner, td.mw-enhanced-rc-nested');
list.each(function(key, current) {
var vandal = $(current).find('.mw-userlink').text();
var href = $(current).find('.mw-changeslist-diff').attr('href');
var rev = mw.util.getParamValue('diff', href);
var page = current.dataset.targetPage;
current.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks(vandal, true, rev, page));
});
}
},
history: function() {
if (Twinkle.getPref('showRollbackLinks').indexOf('history') !== -1) {
// All revs
var histList = $('#pagehistory li').toArray();
// On first page of results, so add revert/rollback
// links to the top revision
if (!$('.mw-firstlink').length) {
var first = histList.shift();
var vandal = first.querySelector('.mw-userlink').text;
first.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks(vandal, true));
}
// oldid
histList.forEach(function(rev) {
// From restoreThisRevision, non-transferable
var href = rev.querySelector('.mw-changeslist-date').href;
var oldid = parseInt(mw.util.getParamValue('oldid', href), 10);
rev.appendChild(Twinkle.fluff.linkBuilder.restoreThisRevisionLink(oldid, true));
});
}
},
diff: function() {
// Autofill user talk links on diffs with vanarticle for easy warning, but don't autowarn
var warnFromTalk = function(xtitle) {
var talkLink = $('#mw-diff-' + xtitle + '2 .mw-usertoollinks a').first();
if (talkLink.length) {
var extraParams = 'vanarticle=' + mw.util.rawurlencode(Morebits.pageNameNorm) + '&' + 'noautowarn=true';
// diffIDs for vanarticlerevid
extraParams += '&vanarticlerevid=';
extraParams += xtitle === 'otitle' ? mw.config.get('wgDiffOldId') : mw.config.get('wgDiffNewId');
var href = talkLink.attr('href');
if (href.indexOf('?') === -1) {
talkLink.attr('href', href + '?' + extraParams);
} else {
talkLink.attr('href', href + '&' + extraParams);
}
}
};
// Older revision
warnFromTalk('otitle'); // Add quick-warn link to user talk link
// Don't load if there's a single revision or weird diff (cur on latest)
if (mw.config.get('wgDiffOldId') && (mw.config.get('wgDiffOldId') !== mw.config.get('wgDiffNewId'))) {
// Add a [restore this revision] link to the older revision
var oldTitle = document.getElementById('mw-diff-otitle1').parentNode;
oldTitle.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgDiffOldId'), oldTitle.firstChild);
}
// Newer revision
warnFromTalk('ntitle'); // Add quick-warn link to user talk link
// Add either restore or rollback links to the newer revision
// Don't show if there's a single revision or weird diff (prev on first)
if (document.getElementById('differences-nextlink')) {
// Not latest revision, add [restore this revision] link to newer revision
var newTitle = document.getElementById('mw-diff-ntitle1').parentNode;
newTitle.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgDiffNewId'), newTitle.firstChild);
} else if (Twinkle.getPref('showRollbackLinks').indexOf('diff') !== -1 && mw.config.get('wgDiffOldId') && (mw.config.get('wgDiffOldId') !== mw.config.get('wgDiffNewId') || document.getElementById('differences-prevlink'))) {
var vandal = $('#mw-diff-ntitle2').find('a').first().text();
var ntitle = document.getElementById('mw-diff-ntitle1').parentNode;
ntitle.insertBefore(Twinkle.fluff.linkBuilder.rollbackLinks(vandal), ntitle.firstChild);
}
},
oldid: function() { // Add a [restore this revision] link on old revisions
var title = document.getElementById('mw-revision-info').parentNode;
title.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgRevisionId'), title.firstChild);
}
};
Twinkle.fluff.disableLinks = function disablelinks(parentNode) {
// Array.from not available in IE11 :(
$(parentNode).children().each(function(_ix, node) {
node.innerHTML = node.textContent; // Feels like cheating
$(node).css('font-weight', 'normal').css('color', 'darkgray');
});
};
Twinkle.fluff.revert = function revertPage(type, vandal, rev, page) {
if (mw.util.isIPv6Address(vandal)) {
vandal = Morebits.sanitizeIPv6(vandal);
}
var pagename = page || mw.config.get('wgPageName');
var revid = rev || mw.config.get('wgCurRevisionId');
if (Twinkle.fluff.rollbackInPlace) {
var notifyStatus = document.createElement('span');
mw.notify(notifyStatus, {
autoHide: false,
title: 'برگردانی روی ' + page, // localized
tag: 'twinklefluff_' + rev // Shouldn't be necessary given disableLink
});
Morebits.status.init(notifyStatus);
} else {
Morebits.status.init(document.getElementById('mw-content-text'));
$('#catlinks').remove();
}
var params = {
type: type,
user: vandal,
pagename: pagename,
revid: revid
};
var query = {
'action': 'query',
'prop': ['info', 'revisions', 'flagged'],
'titles': pagename,
'intestactions': 'edit',
'rvlimit': 50, // intentionally limited
'rvprop': [ 'ids', 'timestamp', 'user', 'comment' ],
'curtimestamp': '',
'meta': 'tokens',
'type': 'csrf'
};
var wikipedia_api = new Morebits.wiki.api('دریافت اطلاعات نسخهٔ قدیمیتر', query, Twinkle.fluff.callbacks.main); // localized
wikipedia_api.params = params;
wikipedia_api.post();
};
Twinkle.fluff.revertToRevision = function revertToRevision(oldrev) {
Morebits.status.init(document.getElementById('mw-content-text'));
var query = {
'action': 'query',
'prop': ['info', 'revisions'],
'titles': mw.config.get('wgPageName'),
'rvlimit': 1,
'rvstartid': oldrev,
'rvprop': [ 'ids', 'timestamp', 'user', 'comment' ],
'format': 'xml',
'curtimestamp': '',
'meta': 'tokens',
'type': 'csrf'
};
var wikipedia_api = new Morebits.wiki.api('دریافت اطلاعات نسخهٔ قدیمیتر', query, Twinkle.fluff.callbacks.toRevision); // localized
wikipedia_api.params = { rev: oldrev };
wikipedia_api.post();
};
Twinkle.fluff.userIpLink = function(user) {
return (mw.util.isIPAddress(user) ? '[[Special:Contributions/' : '[[:User:') + user + '|' + user + ']]';
};
Twinkle.fluff.callbacks = {
toRevision: function(apiobj) {
var xmlDoc = apiobj.responseXML;
var lastrevid = parseInt($(xmlDoc).find('page').attr('lastrevid'), 10);
var touched = $(xmlDoc).find('page').attr('touched');
var loadtimestamp = $(xmlDoc).find('api').attr('curtimestamp');
var csrftoken = $(xmlDoc).find('tokens').attr('csrftoken');
var revertToRevID = parseInt($(xmlDoc).find('rev').attr('revid'), 10);
var revertToUser = $(xmlDoc).find('rev').attr('user');
if (revertToRevID !== apiobj.params.rev) {
apiobj.statelem.error('نسخهای که دریافت شد با نسخهای که درخواست شد مطابقت ندارد. برگردانی متوقف شد.'); // localized
return;
}
var optional_summary = prompt('لطفاً دلیل برگردانی را توضیح دهید: ', ''); // padded out to widen prompt in Firefox // localized
if (optional_summary === null) {
apiobj.statelem.error('توسط کاربر متوقف شد.'); // localized
return;
}
var summary = Twinkle.fluff.formatSummary('به نسخهٔ ' + revertToRevID + ' از $USER برگردانده شد', revertToUser, optional_summary); // localized
var query = {
'action': 'edit',
'title': mw.config.get('wgPageName'),
'summary': summary,
'token': csrftoken,
'undo': lastrevid,
'undoafter': revertToRevID,
'basetimestamp': touched,
'starttimestamp': loadtimestamp,
'watchlist': Twinkle.getPref('watchRevertedPages').indexOf('torev') !== -1 ? 'watch' : undefined,
'minor': Twinkle.getPref('markRevertedPagesAsMinor').indexOf('torev') !== -1 ? true : undefined
};
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'برگردانی موفق بود'; // localized
var wikipedia_api = new Morebits.wiki.api('ذخیرهٔ محتوای برگردانی شده', query, Twinkle.fluff.callbacks.complete, apiobj.statelem); // localized
wikipedia_api.params = apiobj.params;
wikipedia_api.post();
},
main: function(apiobj) {
var xmlDoc = apiobj.responseXML;
if (typeof $(xmlDoc).find('actions').attr('edit') === 'undefined') {
self.statelem.error("Unable to edit the page, it's probably protected.");
return;
}
var lastrevid = parseInt($(xmlDoc).find('page').attr('lastrevid'), 10);
var touched = $(xmlDoc).find('page').attr('touched');
var loadtimestamp = $(xmlDoc).find('api').attr('curtimestamp');
var csrftoken = $(xmlDoc).find('tokens').attr('csrftoken');
var lastuser = $(xmlDoc).find('rev').attr('user');
var revs = $(xmlDoc).find('rev');
var statelem = apiobj.statelem;
var params = apiobj.params;
if (revs.length < 1) {
statelem.error('صفحه تنها یک نسخهٔ دیگر دارد در نتیجه برگردانی ممکن نیست.'); // localiezd
return;
}
var top = revs[0];
if (lastrevid < params.revid) {
Morebits.status.error('Error', [ 'شناسهٔ آخرین نسخه که از سرور دریافت شد ', Morebits.htmlNode('strong', lastrevid), ' بود که از شناسهٔ نسخهٔ نمایش یافته کمتر است. این میتواند نشاندهندهٔ آن باشد که نسخهٔ فعلی حذف شدهاست، سرورها تأخیر دارند، یا دادههایی که دریافت شده اشتباه هستند. برگردانی متوقف شد.' ]); // localized
return;
}
var index = 1;
if (params.revid !== lastrevid) {
Morebits.status.warn('Warning', [ 'نسخهٔ آخر ', Morebits.htmlNode('strong', lastrevid), ' است که با نسخهٔ ما مطابقت ندارد', Morebits.htmlNode('strong', params.revid) ]); // loaclized
if (lastuser === params.user) {
switch (params.type) {
case 'vand':
Morebits.status.info('Info', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', params.user), ' ایجاد شدهاست. از آنجا که فرض بر خرابکاری است، برگردانی ادامه مییابد.' ]); // localizde
break;
case 'agf':
Morebits.status.warn('Warning', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', params.user), ' ایجاد شدهاست. از آنجا که فرض بر حسن نیت است، برگردانی متوقف شد چرا که مشکل ممکن است رفع شده باشد.' ]); // localiezd
return;
default:
Morebits.status.warn('Notice', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', params.user), ' ایجاد شدهاست اما برگردانی متوقف شد.' ]); // localized
return;
}
} else if (params.type === 'vand' &&
Twinkle.fluff.whiteList.indexOf(top.getAttribute('user')) !== -1 && revs.length > 1 &&
revs[1].getAttribute('pageId') === params.revid) {
Morebits.status.info('Info', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', lastuser), ' ایجاد شده که یک ربات مورد اعتماد است. نسخهٔ پیش از آن توسط کاربر خرابکار ایجاد شده لذا برگردانی ادامه مییابد.' ]); // localized
index = 2;
} else {
Morebits.status.error('Error', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', lastuser), ' ایجاد شده پس صفحه قاعدتاً باید برگردانی شده باشد؛ برگردانی متوقف شد.']); // localized
return;
}
}
if (Twinkle.fluff.whiteList.indexOf(params.user) !== -1) {
switch (params.type) {
case 'vand':
Morebits.status.info('Info', [ 'درخواست برگردانی خرابکاری برای ویرایش ', Morebits.htmlNode('strong', params.user), ' داده شده بود. از آنجا که این حساب، یک ربات مورد اعتماد است، فرض شد که منظور شما برگردانی خرابکاری کاربری بود که ویرایش قبل از ربات را انجام داده بود.' ]); // localized
index = 2;
params.user = revs[1].getAttribute('user');
break;
case 'agf':
Morebits.status.warn('Notice', [ 'درخواست برگردانی ویرایش ', Morebits.htmlNode('strong', params.user), ' با فرض حسن نیت داده شده بود. این حساب، یک ربات مورد اعتماد است در نتیجه برگردانی متوقف شد.' ]); // localized
return;
case 'norm':
/* falls through */
default:
var cont = confirm('برگردانی عادی انتخاب شده بود، اما بیشتر تغییرات توسط یک ربات مورد اعتماد (' + params.user + ') انجام شدهاست. آیا میخواهید به نسخهٔ پیش از تغییرات ربات برگردانی کنید؟'); // localized
if (cont) {
Morebits.status.info('Info', [ 'درخواست برگردانی عادی ویرایشهای ', Morebits.htmlNode('strong', params.user), ' داده شده بود. این حساب، یک ربات مورد اعتماد است و به خواست شما، برگردانی به نسخهٔ پیش از ویرایشهای ربات انجام شد.' ]); // localized
index = 2;
params.user = revs[1].getAttribute('user');
} else {
Morebits.status.warn('Notice', [ 'درخواست برگردانی عادی ویرایشهای ', Morebits.htmlNode('strong', params.user), ' داده شدهبود. این حساب، یک ربات مورد اعتماد است اما به خواست شما ویرایشهایش برگردانی شد.' ]); // localized
}
break;
}
}
var found = false;
var count = 0;
for (var i = index; i < revs.length; ++i) {
++count;
if (revs[i].getAttribute('user') !== params.user) {
found = i;
break;
}
}
if (!found) {
statelem.error([ 'نسخهٔ قدیمیتری یافت نشد. شاید ', Morebits.htmlNode('strong', params.user), ' تنها کاربری است که در صفحه مشارکت کرده، یا بیش از ' + mw.language.convertNumber(Twinkle.getPref('revertMaxRevisions')) + ' ویرایش پیاپی در صفحه انجام دادهاست.' ]); // localized
return;
}
if (!count) {
Morebits.status.error('Error', 'از آنجا که برگردانی ویرایشهای خنثی ممکن نیست، برگردانی متوقف شد. ممکن است ویرایش پیشتر برگردانی شده باشد اگر چه شناسهٔ ویرایش هنوز همان باشد.'); // localized
return;
}
var good_revision = revs[found];
var userHasAlreadyConfirmedAction = false;
if (params.type !== 'vand' && count > 1) {
if (!confirm(params.user + ' به طور پیاپی ' + mw.language.convertNumber(count) + ' ویرایش روی صفحه کردهاست. آیا میخواهید همهٔ آنها را برگردانی کنید؟')) { // localized
Morebits.status.info('Notice', 'برگردانی متوقف شد.'); // localized
return;
}
userHasAlreadyConfirmedAction = true;
}
params.count = count;
params.goodid = good_revision.getAttribute('revid');
params.gooduser = good_revision.getAttribute('user');
statelem.status([ 'نسخهٔ ', Morebits.htmlNode('strong', params.goodid), ' که ', Morebits.htmlNode('strong', mw.language.convertNumber(count)), ' نسخه پیشتر توسط ', Morebits.htmlNode('strong', params.gooduser) , ' ثبت شده بود']); // localized
var summary, extra_summary;
switch (params.type) {
case 'agf':
extra_summary = prompt('توضیح اختیاری برای خلاصه ویرایش: ', ''); // padded out to widen prompt in Firefox // localized
if (extra_summary === null) {
statelem.error('توسط کاربر متوقف شد.'); // localized
return;
}
userHasAlreadyConfirmedAction = true;
summary = Twinkle.fluff.formatSummary('ویرایشهای دارای [[وو:فحن|حسن نیت]] $USER برگردانی شد', params.user, extra_summary); // localized
break;
case 'vand':
summary = mw.language.convertNumber(params.count) + (params.count > 1 ? ' ویرایش' : ' ویرایش') + ' از [[Special:Contributions/' + // localized
params.user + '|' + params.user + ']] ([[User talk:' + params.user + '|بحث]]) به آخرین نسخه توسط ' + // localized
params.gooduser + ' برگردانی شد' + Twinkle.getPref('summaryAd'); // localized
break;
case 'norm':
/* falls through */
default:
if (Twinkle.getPref('offerReasonOnNormalRevert')) {
extra_summary = prompt('توضیح اختیاری برای خلاصه ویرایش: ', ''); // padded out to widen prompt in Firefox // localized
if (extra_summary === null) {
statelem.error('توسط کاربر متوقف شد.'); // localized
return;
}
userHasAlreadyConfirmedAction = true;
}
summary = Twinkle.fluff.formatSummary(mw.language.convertNumber(params.count) + (params.count > 1 ? ' ویرایش' : ' ویرایش') + // localized
' $USER برگردانی شد', params.user, extra_summary); // localized
break;
}
if (Twinkle.getPref('confirmOnFluff') && !userHasAlreadyConfirmedAction && !confirm('برگردانی صفحه: آیا مطمئن هستید؟')) { // localized
statelem.error('توسط کاربر متوقف شد.'); // localized
return;
}
// Decide whether to notify the user on success
if (!Twinkle.fluff.skipTalk && Twinkle.getPref('openTalkPage').indexOf(params.type) !== -1 &&
mw.config.get('wgUserName') !== params.user) {
params.notifyUser = true;
}
// figure out whether we need to/can review the edit
var $flagged = $(xmlDoc).find('flagged');
if ((Morebits.userIsInGroup('reviewer') || Morebits.userIsSysop) &&
$flagged.length &&
$flagged.attr('stable_revid') >= params.goodid &&
$flagged.attr('pending_since')) {
params.reviewRevert = true;
params.csrftoken = csrftoken;
}
var query = {
'action': 'edit',
'title': params.pagename,
'summary': summary,
'token': csrftoken,
'undo': lastrevid,
'undoafter': params.goodid,
'basetimestamp': touched,
'starttimestamp': loadtimestamp,
'watchlist': Twinkle.getPref('watchRevertedPages').indexOf(params.type) !== -1 ? 'watch' : undefined,
'minor': Twinkle.getPref('markRevertedPagesAsMinor').indexOf(params.type) !== -1 ? true : undefined
};
if (!Twinkle.fluff.rollbackInPlace) {
Morebits.wiki.actionCompleted.redirect = params.pagename;
}
Morebits.wiki.actionCompleted.notice = 'برگردانی موفق بود'; // localized
var wikipedia_api = new Morebits.wiki.api('در حال ذخیره کردن محتوای برگردانی شده', query, Twinkle.fluff.callbacks.complete, statelem); // localized
wikipedia_api.params = params;
wikipedia_api.post();
},
complete: function (apiobj) {
// TODO Most of this is copy-pasted from Morebits.wiki.page#fnSaveSuccess. Unify it
var xml = apiobj.getXML();
var $edit = $(xml).find('edit');
if ($(xml).find('captcha').length > 0) {
apiobj.statelem.error('برگردانی موفق نبود، چون سرور ویکی درخواست کرد که شما یک کپچا پر کنید.'); // localized
} else if ($edit.attr('nochange') === '') {
apiobj.statelem.error('نسخهای که به آن برگردانی میکنیم با نسخهٔ فعلی یکسان است؛ برگردانی متوقف شد.'); // localized
} else {
apiobj.statelem.info('done');
var params = apiobj.params;
if (params.notifyUser) { // Only from main, not from toRevision
Morebits.status.info('Info', [ 'باز کردن فرم ویرایش صفحهٔ بحث کاربر ', Morebits.htmlNode('strong', params.user) ]); // localized
var windowQuery = {
'title': 'User talk:' + params.user,
'action': 'edit',
'preview': 'yes',
'vanarticle': params.pagename.replace(/_/g, ' '),
'vanarticlerevid': params.revid,
'vanarticlegoodrevid': params.goodid,
'type': params.type,
'count': params.count
};
switch (Twinkle.getPref('userTalkPageMode')) {
case 'tab':
window.open(mw.util.getUrl('', windowQuery), '_blank');
break;
case 'blank':
window.open(mw.util.getUrl('', windowQuery), '_blank',
'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800');
break;
case 'window':
/* falls through */
default:
window.open(mw.util.getUrl('', windowQuery),
window.name === 'twinklewarnwindow' ? '_blank' : 'twinklewarnwindow',
'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800');
break;
}
}
// review the revert, if needed
if (apiobj.params.reviewRevert) {
var query = {
'action': 'review',
'revid': $edit.attr('newrevid'),
'token': apiobj.params.csrftoken,
'comment': Twinkle.getPref('summaryAd').trim()
};
var wikipedia_api = new Morebits.wiki.api('پذیرش خودکار تغییرات شما', query); // localized
wikipedia_api.post();
}
}
}
};
// builtInString should contain the string "$USER", which will be replaced
// by an appropriate user link
Twinkle.fluff.formatSummary = function(builtInString, userName, userString) {
var result = builtInString;
// append user's custom reason
if (userString) {
result += ': ' + Morebits.string.toUpperCaseFirstChar(userString);
}
result += Twinkle.getPref('summaryAd');
// find number of UTF-8 bytes the resulting string takes up, and possibly add
// a contributions or contributions+talk link if it doesn't push the edit summary
// over the 255-byte limit
var resultLen = unescape(encodeURIComponent(result.replace('$USER', ''))).length;
var contribsLink = '[[Special:Contributions/' + userName + '|' + userName + ']]';
var contribsLen = unescape(encodeURIComponent(contribsLink)).length;
if (resultLen + contribsLen <= 255) {
var talkLink = ' ([[User talk:' + userName + '|بحث]])'; // localized
if (resultLen + contribsLen + unescape(encodeURIComponent(talkLink)).length <= 255) {
result = Morebits.string.safeReplace(result, '$USER', contribsLink + talkLink);
} else {
result = Morebits.string.safeReplace(result, '$USER', contribsLink);
}
} else {
result = Morebits.string.safeReplace(result, '$USER', userName);
}
return result;
};
})(jQuery);
// </nowiki>
/* ===== twinklebatchdelete.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklebatchdelete.js: Batch delete module (sysops only)
****************************************
* Mode of invocation: Tab ("D-batch")
* Active on: Existing non-articles, and Special:PrefixIndex
*/
Twinkle.batchdelete = function twinklebatchdelete() {
if (
Morebits.userIsSysop && (
(mw.config.get('wgCurRevisionId') && mw.config.get('wgNamespaceNumber') > 0) ||
mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex'
)
) {
Twinkle.addPortletLink(Twinkle.batchdelete.callback, 'حذف دستهجمعی', 'tw-batch', 'حذف صفحههایی که در این صفحه پیوند دارند یا در این رده قرار دارند'); // localized
}
};
Twinkle.batchdelete.unlinkCache = {};
// Has the subpages list been loaded?
var subpagesLoaded;
Twinkle.batchdelete.callback = function twinklebatchdeleteCallback() {
subpagesLoaded = false;
var Window = new Morebits.simpleWindow(600, 400);
Window.setTitle('حذف دستهجمعی'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#batchdelete'); // localized
var form = new Morebits.quickForm(Twinkle.batchdelete.callback.evaluate);
form.append({
type: 'checkbox',
list: [
{
label: 'حذف صفحهها', // localized
name: 'delete_page',
value: 'delete',
checked: true,
subgroup: {
type: 'checkbox',
list: [
{
label: 'حذف صفحهٔ بحث مرتبط (به جز صفحههای بحث کاربر)', // localized
name: 'delete_talk',
value: 'delete_talk',
checked: true
},
{
label: 'حذف تغییرمسیرها به صفحههای انتخاب شده', // localized
name: 'delete_redirects',
value: 'delete_redirects',
checked: true
},
{
label: 'حذف زیرصفحههای صفحههای انتخاب شده', // localized
name: 'delete_subpages',
value: 'delete_subpages',
checked: false,
event: Twinkle.batchdelete.callback.toggleSubpages,
subgroup: {
type: 'checkbox',
list: [
{
label: 'حذف صفحههای بحث زیرصفحههای مذکور', // localized
name: 'delete_subpage_talks',
value: 'delete_subpage_talks'
},
{
label: 'حذف تغییرمسیرها به زیرصفحههای مذکور', // localized
name: 'delete_subpage_redirects',
value: 'delete_subpage_redirects'
},
{
label: 'زدودن پیوندها به زیرصفحههای مذکور (فقط در فضای نام اصلی و درگاه)', // localized
name: 'unlink_subpages',
value: 'unlink_subpages'
}
]
}
}
]
}
},
{
label: 'زدودن پیوندها به صفحههای انتخاب شده (فقط در فضای نام اصلی و درگاه)', // localized
name: 'unlink_page',
value: 'unlink',
checked: false
},
{
label: 'حذف کاربردهای پروندههای انتخاب شده (در تمام فضاهای نام)', // localized
name: 'unlink_file',
value: 'unlink_file',
checked: true
}
]
});
form.append({
type: 'input',
name: 'reason',
label: 'دلیل: ', // localized
size: 60
});
var query = {
'action': 'query',
'prop': 'revisions|info|imageinfo',
'inprop': 'protection',
'rvprop': 'size|user'
};
// On categories
if (mw.config.get('wgNamespaceNumber') === 14) {
query.generator = 'categorymembers';
query.gcmtitle = mw.config.get('wgPageName');
query.gcmlimit = Twinkle.getPref('batchMax');
// On Special:PrefixIndex
} else if (mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex') {
query.generator = 'allpages';
query.gaplimit = Twinkle.getPref('batchMax');
if (mw.util.getParamValue('prefix')) {
query.gapnamespace = mw.util.getParamValue('namespace');
query.gapprefix = mw.util.getParamValue('prefix');
} else {
var pathSplit = decodeURIComponent(location.pathname).split('/');
if (pathSplit.length < 3 || pathSplit[2] !== 'Special:PrefixIndex') {
return;
}
var titleSplit = pathSplit[3].split(':');
query.gapnamespace = mw.config.get('wgNamespaceIds')[titleSplit[0].toLowerCase()];
if (titleSplit.length < 2 || typeof query.gapnamespace === 'undefined') {
query.gapnamespace = 0; // article namespace
query.gapprefix = pathSplit.splice(3).join('/');
} else {
pathSplit = pathSplit.splice(4);
pathSplit.splice(0, 0, titleSplit.splice(1).join(':'));
query.gapprefix = pathSplit.join('/');
}
}
// On normal pages
} else {
query.generator = 'links';
query.titles = mw.config.get('wgPageName');
query.gpllimit = Twinkle.getPref('batchMax');
}
var statusdiv = document.createElement('div');
statusdiv.style.padding = '15px'; // just so it doesn't look broken
Window.setContent(statusdiv);
Morebits.status.init(statusdiv);
Window.display();
Twinkle.batchdelete.pages = {};
var statelem = new Morebits.status('گرفتن فهرست صفحهها'); // localized
var wikipedia_api = new Morebits.wiki.api('در حال بارگیری...', query, function(apiobj) { // localized
var xml = apiobj.responseXML;
var $pages = $(xml).find('page').filter(':not([missing])'); // :not([imagerepository="shared"])
$pages.each(function(index, page) {
var $page = $(page);
var ns = $page.attr('ns');
var title = $page.attr('title');
var isRedir = $page.attr('redirect') === '';
var $editprot = $page.find('pr[type="edit"][level="sysop"]');
var isProtected = $editprot.length > 0;
var size = $page.find('rev').attr('size');
var metadata = [];
if (isRedir) {
metadata.push('تغییرمسیر'); // localized
}
if (isProtected) {
metadata.push('محافظت کامل' + // localized
($editprot.attr('expiry') === 'infinity' ? ' بیپایان' : '، انقضا در ' + new Morebits.date($editprot.attr('expiry')).calendar('utc') + ' (UTC)')); // localized
}
if (ns === '6') { // mimic what delimages used to show for files
metadata.push('بارگذاری توسط: ' + $page.find('ii').attr('user')); // localized
metadata.push('آخرین ویرایش توسط: ' + $page.find('rev').attr('user')); // localized
} else {
metadata.push(mw.language.convertNumber(size) + ' بایت'); // localized
}
Twinkle.batchdelete.pages[title] = {
label: title + (metadata.length ? ' (' + metadata.join('؛ ') + ')' : ''), // localized
value: title,
checked: true,
style: isProtected ? 'color:red' : ''
};
});
var form = apiobj.params.form;
form.append({ type: 'header', label: 'صفحهها برای حذف' }); // localized
form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function dBatchSelectAll() {
$(result).find('input[name=pages]:not(:checked)').each(function(_, e) {
e.click(); // check it, and invoke click event so that subgroup can be shown
});
// Check any unchecked subpages too
$('input[name="pages.subpages"]').prop('checked', true);
}
});
form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function dBatchDeselectAll() {
$(result).find('input[name=pages]:checked').each(function(_, e) {
e.click(); // uncheck it, and invoke click event so that subgroup can be hidden
});
}
});
form.append({
type: 'checkbox',
name: 'pages',
id: 'tw-dbatch-pages',
list: $.map(Twinkle.batchdelete.pages, function (e) {
return e;
})
});
form.append({ type: 'submit' });
var result = form.render();
apiobj.params.Window.setContent(result);
var pageCheckboxes = Morebits.quickForm.getElements(result, 'pages') || [];
pageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(pageCheckboxes);
}, statelem);
wikipedia_api.params = { form: form, Window: Window };
wikipedia_api.post();
};
function generateArrowLinks (checkbox) {
var link = Morebits.htmlNode('a', ' >');
link.setAttribute('class', 'tw-dbatch-page-link');
link.setAttribute('href', mw.util.getUrl(checkbox.value));
link.setAttribute('target', '_blank');
checkbox.nextElementSibling.append(link);
}
Twinkle.batchdelete.generateNewPageList = function(form) {
// Update the list of checked pages in Twinkle.batchdelete.pages object
var elements = form.elements.pages;
if (elements instanceof NodeList) { // if there are multiple pages
for (var i = 0; i < elements.length; ++i) {
Twinkle.batchdelete.pages[elements[i].value].checked = elements[i].checked;
}
} else if (elements instanceof HTMLInputElement) { // if there is just one page
Twinkle.batchdelete.pages[elements.value].checked = elements.checked;
}
return new Morebits.quickForm.element({
type: 'checkbox',
name: 'pages',
id: 'tw-dbatch-pages',
list: $.map(Twinkle.batchdelete.pages, function (e) {
return e;
})
}).render();
};
Twinkle.batchdelete.callback.toggleSubpages = function twDbatchToggleSubpages(e) {
var form = e.target.form;
var newPageList, pageCheckboxes, subpageCheckboxes;
if (e.target.checked) {
form.delete_subpage_redirects.checked = form.delete_redirects.checked;
form.delete_subpage_talks.checked = form.delete_talk.checked;
form.unlink_subpages.checked = form.unlink_page.checked;
// If lists of subpages were already loaded once, they are
// available without use of any API calls
if (subpagesLoaded) {
$.each(Twinkle.batchdelete.pages, function(i, el) {
// Get back the subgroup from subgroup_, where we saved it
if (el.subgroup === null && el.subgroup_) {
el.subgroup = el.subgroup_;
}
});
newPageList = Twinkle.batchdelete.generateNewPageList(form);
$('#tw-dbatch-pages').replaceWith(newPageList);
pageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages') || [];
pageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(pageCheckboxes);
subpageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages.subpages') || [];
subpageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(subpageCheckboxes);
return;
}
// Proceed with API calls to get list of subpages
var loadingText = '<strong id="dbatch-subpage-loading">در حال بارگیری... </strong>'; // localized
$(e.target).after(loadingText);
var pages = $(form.pages).map(function(i, el) {
return el.value;
}).get();
var subpageLister = new Morebits.batchOperation();
subpageLister.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
subpageLister.setPageList(pages);
subpageLister.run(function worker (pageName) {
var pageTitle = mw.Title.newFromText(pageName);
// No need to look for subpages in main/file/mediawiki space
if ([0, 6, 8].indexOf(pageTitle.namespace) > -1) {
subpageLister.workerSuccess();
return;
}
var wikipedia_api = new Morebits.wiki.api('گرفتن فهرست زیرصفحههای ' + pageName, { // localized
action: 'query',
prop: 'revisions|info|imageinfo',
generator: 'allpages',
rvprop: 'size',
inprop: 'protection',
gapprefix: pageTitle.title + '/',
gapnamespace: pageTitle.namespace,
gaplimit: 'max', // 500 is max for normal users, 5000 for bots and sysops
pageNameFull: pageName // Not used by API, but added for access in onSuccess()
}, function onSuccess(apiobj) {
var xml = apiobj.responseXML;
var $pages = $(xml).find('page');
var subpageList = [];
$pages.each(function(index, page) {
var $page = $(page);
var ns = $page.attr('ns');
var title = $page.attr('title');
var isRedir = $page.attr('redirect') === '';
var $editprot = $page.find('pr[type="edit"][level="sysop"]');
var isProtected = $editprot.length > 0;
var size = $page.find('rev').attr('size');
var metadata = [];
if (isRedir) {
metadata.push('تغییرمسیر'); // localized
}
if (isProtected) {
metadata.push('محافظت کامل' + // localized
($editprot.attr('expiry') === 'infinity' ? ' بیپایان' : '، انقضا در ' + new Morebits.date($editprot.attr('expiry')).calendar('utc') + ' (UTC)')); // localized
}
if (ns === '6') { // mimic what delimages used to show for files
metadata.push('بارگذاری توسط: ' + $page.find('ii').attr('user')); //localized
metadata.push('آخرین ویرایش توسط: ' + $page.find('rev').attr('user')); // localized
} else {
metadata.push(mw.language.convertNumber(size) + ' بایت'); // localized
}
subpageList.push({
label: title + (metadata.length ? ' (' + metadata.join('؛ ') + ')' : ''), // localized
value: title,
checked: true,
style: isProtected ? 'color:red' : ''
});
});
if (subpageList.length) {
var pageName = apiobj.query.pageNameFull;
Twinkle.batchdelete.pages[pageName].subgroup = {
type: 'checkbox',
name: 'subpages',
className: 'dbatch-subpages',
list: subpageList
};
}
subpageLister.workerSuccess();
}, null /* statusElement */, function onFailure() {
subpageLister.workerFailure();
});
wikipedia_api.post();
}, function postFinish () {
// List 'em on the interface
newPageList = Twinkle.batchdelete.generateNewPageList(form);
$('#tw-dbatch-pages').replaceWith(newPageList);
pageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages') || [];
pageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(pageCheckboxes);
subpageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages.subpages') || [];
subpageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(subpageCheckboxes);
subpagesLoaded = true;
// Remove "Loading... " text
$('#dbatch-subpage-loading').remove();
});
} else if (!e.target.checked) {
$.each(Twinkle.batchdelete.pages, function(i, el) {
if (el.subgroup) {
// Remove subgroup after saving its contents in subgroup_
// so that it can be retrieved easily if user decides to
// delete the subpages again
el.subgroup_ = el.subgroup;
el.subgroup = null;
}
});
newPageList = Twinkle.batchdelete.generateNewPageList(form);
$('#tw-dbatch-pages').replaceWith(newPageList);
pageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages') || [];
pageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(pageCheckboxes);
}
};
Twinkle.batchdelete.callback.evaluate = function twinklebatchdeleteCallbackEvaluate(event) {
Morebits.wiki.actionCompleted.notice = 'حذف دستهجمعی کامل شد'; // localized
var form = event.target;
var numProtected = $(Morebits.quickForm.getElements(form, 'pages')).filter(function(index, element) {
return element.checked && element.nextElementSibling.style.color === 'red';
}).length;
if (numProtected > 0 && !confirm('شما در حال حذف ' + mw.language.convertNumber(numProtected) + ' صفحهٔ کاملاً محافظتشده هستید. آیا مطمئن هستید؟')) { // localized
return;
}
var pages = form.getChecked('pages');
var subpages = form.getChecked('pages.subpages');
var reason = form.reason.value;
var delete_page = form.delete_page.checked;
var delete_talk, delete_redirects, delete_subpages;
var delete_subpage_redirects, delete_subpage_talks, unlink_subpages;
if (delete_page) {
delete_talk = form.delete_talk.checked;
delete_redirects = form.delete_redirects.checked;
delete_subpages = form.delete_subpages.checked;
if (delete_subpages) {
delete_subpage_redirects = form.delete_subpage_redirects.checked;
delete_subpage_talks = form.delete_subpage_talks.checked;
unlink_subpages = form.unlink_subpages.checked;
}
}
var unlink_page = form.unlink_page.checked;
var unlink_file = form.unlink_file.checked;
if (!reason) {
alert('شما باید دلیلی ارائه کنید!'); // localized
return;
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
if (!pages) {
Morebits.status.error('Error', 'چیزی برای حذف وجود نداشت؛ حذف متوقف شد'); // localized
return;
}
var pageDeleter = new Morebits.batchOperation(delete_page ? 'حذف صفحهها' : 'شروع وظایف درخواستشده'); // localized
pageDeleter.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
// we only need the initial status lines if we're deleting the pages in the pages array
pageDeleter.setOption('preserveIndividualStatusLines', delete_page);
pageDeleter.setPageList(pages);
pageDeleter.run(function worker(pageName) {
var params = {
page: pageName,
delete_page: delete_page,
delete_talk: delete_talk,
delete_redirects: delete_redirects,
unlink_page: unlink_page,
unlink_file: unlink_file && /^(File|Image|پرونده|تصویر):/i.test(pageName), // localized
reason: reason,
pageDeleter: pageDeleter
};
var wikipedia_page = new Morebits.wiki.page(pageName, 'حذف صفحهٔ ' + pageName); // localized
wikipedia_page.setCallbackParameters(params);
if (delete_page) {
wikipedia_page.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
wikipedia_page.suppressProtectWarning();
wikipedia_page.deletePage(Twinkle.batchdelete.callbacks.doExtras, pageDeleter.workerFailure);
} else {
Twinkle.batchdelete.callbacks.doExtras(wikipedia_page);
}
}, function postFinish() {
if (delete_subpages) {
var subpageDeleter = new Morebits.batchOperation('حذف زیرصفحهها'); // localized
subpageDeleter.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
subpageDeleter.setOption('preserveIndividualStatusLines', true);
subpageDeleter.setPageList(subpages);
subpageDeleter.run(function(pageName) {
var params = {
page: pageName,
delete_page: true,
delete_talk: delete_subpage_talks,
delete_redirects: delete_subpage_redirects,
unlink_page: unlink_subpages,
unlink_file: false,
reason: reason,
pageDeleter: subpageDeleter
};
var wikipedia_page = new Morebits.wiki.page(pageName, 'حذف زیرصفحه با عنوان ' + pageName); // localized
wikipedia_page.setCallbackParameters(params);
wikipedia_page.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
wikipedia_page.suppressProtectWarning();
wikipedia_page.deletePage(Twinkle.batchdelete.callbacks.doExtras, pageDeleter.workerFailure);
});
}
});
};
Twinkle.batchdelete.callbacks = {
// this stupid parameter name is a temporary thing until I implement an overhaul
// of Morebits.wiki.* callback parameters
doExtras: function(thingWithParameters) {
var params = thingWithParameters.parent ? thingWithParameters.parent.getCallbackParameters() :
thingWithParameters.getCallbackParameters();
// the initial batch operation's job is to delete the page, and that has
// succeeded by now
params.pageDeleter.workerSuccess(thingWithParameters);
var query, wikipedia_api;
if (params.unlink_page) {
Twinkle.batchdelete.unlinkCache = {};
query = {
'action': 'query',
'list': 'backlinks',
'blfilterredir': 'nonredirects',
'blnamespace': [0, 100], // main space and portal space only
'bltitle': params.page,
'bllimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
wikipedia_api = new Morebits.wiki.api('گرفتن پیوندهای ورودی', query, Twinkle.batchdelete.callbacks.unlinkBacklinksMain); // localized
wikipedia_api.params = params;
wikipedia_api.post();
}
if (params.unlink_file) {
query = {
'action': 'query',
'list': 'imageusage',
'iutitle': params.page,
'iulimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
wikipedia_api = new Morebits.wiki.api('گرفتن پیوندها به پرونده', query, Twinkle.batchdelete.callbacks.unlinkImageInstancesMain); // localized
wikipedia_api.params = params;
wikipedia_api.post();
}
if (params.delete_page) {
if (params.delete_redirects) {
query = {
'action': 'query',
'titles': params.page,
'prop': 'redirects',
'rdlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
wikipedia_api = new Morebits.wiki.api('گرفتن تغییرمسیرها', query, Twinkle.batchdelete.callbacks.deleteRedirectsMain); // localized
wikipedia_api.params = params;
wikipedia_api.post();
}
if (params.delete_talk) {
var pageTitle = mw.Title.newFromText(params.page);
if (pageTitle && pageTitle.namespace % 2 === 0 && pageTitle.namespace !== 2) {
pageTitle.namespace++; // now pageTitle is the talk page title!
query = {
'action': 'query',
'titles': pageTitle.toText()
};
wikipedia_api = new Morebits.wiki.api('بررسی این که صفحهٔ بحثی وجود دارد یانه', query, Twinkle.batchdelete.callbacks.deleteTalk); // localized
wikipedia_api.params = params;
wikipedia_api.params.talkPage = pageTitle.toText();
wikipedia_api.post();
}
}
}
},
deleteRedirectsMain: function(apiobj) {
var xml = apiobj.responseXML;
var pages = $(xml).find('rd').map(function() {
return $(this).attr('title');
}).get();
if (!pages.length) {
return;
}
var redirectDeleter = new Morebits.batchOperation('حذف تغییرمسیرها به ' + apiobj.params.page); // localized
redirectDeleter.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
redirectDeleter.setPageList(pages);
redirectDeleter.run(function(pageName) {
var wikipedia_page = new Morebits.wiki.page(pageName, 'حذف ' + pageName); // localized
wikipedia_page.setEditSummary('[[وو:محس#ع۸|ع۸]]: تغییرمسیر به صفحهٔ حذف شده «' + apiobj.params.page + '»' + Twinkle.getPref('deletionSummaryAd')); // localized
wikipedia_page.deletePage(redirectDeleter.workerSuccess, redirectDeleter.workerFailure);
});
},
deleteTalk: function(apiobj) {
var xml = apiobj.responseXML;
var exists = $(xml).find('page:not([missing])').length > 0;
if (!exists) {
// no talk page; forget about it
return;
}
var page = new Morebits.wiki.page(apiobj.params.talkPage, 'حذف صفحهٔ بحث ' + apiobj.params.page); // localized
page.setEditSummary('[[وو:محس#ع۸|ع۸]]: [[راهنما: صفحه بحث|صفحهٔ بحث]] صفحهٔ حذف شده «' + apiobj.params.page + '»' + Twinkle.getPref('deletionSummaryAd')); // localized
page.deletePage();
},
unlinkBacklinksMain: function(apiobj) {
var xml = apiobj.responseXML;
var pages = $(xml).find('bl').map(function() {
return $(this).attr('title');
}).get();
if (!pages.length) {
return;
}
var unlinker = new Morebits.batchOperation('زدودن پیوندهای ورودی به ' + apiobj.params.page); // localized
unlinker.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
unlinker.setPageList(pages);
unlinker.run(function(pageName) {
var wikipedia_page = new Morebits.wiki.page(pageName, 'زدودن پیوند از ' + pageName); // localized
var params = $.extend({}, apiobj.params);
params.title = pageName;
params.unlinker = unlinker;
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkBacklinks);
});
},
unlinkBacklinks: function(pageobj) {
var params = pageobj.getCallbackParameters();
if (!pageobj.exists()) {
// we probably just deleted it, as a recursive backlink
params.unlinker.workerSuccess(pageobj);
return;
}
var text;
if (params.title in Twinkle.batchdelete.unlinkCache) {
text = Twinkle.batchdelete.unlinkCache[params.title];
} else {
text = pageobj.getPageText();
}
var old_text = text;
var wikiPage = new Morebits.wikitext.page(text);
wikiPage.removeLink(params.page);
text = wikiPage.getText();
Twinkle.batchdelete.unlinkCache[params.title] = text;
if (text === old_text) {
// Nothing to do, return
params.unlinker.workerSuccess(pageobj);
return;
}
pageobj.setEditSummary('زدودن پیوندها به صفحهٔ حذف شده ' + params.page + Twinkle.getPref('deletionSummaryAd')); // localized
pageobj.setPageText(text);
pageobj.setCreateOption('nocreate');
pageobj.setMaxConflictRetries(10);
pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
},
unlinkImageInstancesMain: function(apiobj) {
var xml = apiobj.responseXML;
var pages = $(xml).find('iu').map(function() {
return $(this).attr('title');
}).get();
if (!pages.length) {
return;
}
var unlinker = new Morebits.batchOperation('حذف پیوندهای ورودی به ' + apiobj.params.page); // localized
unlinker.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
unlinker.setPageList(pages);
unlinker.run(function(pageName) {
var wikipedia_page = new Morebits.wiki.page(pageName, 'حذف کاربردهای پرونده در ' + pageName); // localized
var params = $.extend({}, apiobj.params);
params.title = pageName;
params.unlinker = unlinker;
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkImageInstances);
});
},
unlinkImageInstances: function(pageobj) {
var params = pageobj.getCallbackParameters();
if (!pageobj.exists()) {
// we probably just deleted it, as a recursive backlink
params.unlinker.workerSuccess(pageobj);
return;
}
var image = params.page.replace(/^(?:Image|File|تصویر|پرونده):/, ''); // localized
var text;
if (params.title in Twinkle.batchdelete.unlinkCache) {
text = Twinkle.batchdelete.unlinkCache[params.title];
} else {
text = pageobj.getPageText();
}
var old_text = text;
var wikiPage = new Morebits.wikitext.page(text);
wikiPage.commentOutImage(image, 'کاربرد تصویر حذف شده پنهان شد'); // localized
text = wikiPage.getText();
Twinkle.batchdelete.unlinkCache[params.title] = text;
if (text === old_text) {
pageobj.getStatusElement().error('حذف کاربرد ' + image + ' از ' + pageobj.getPageName() + ' ناموفق بود'); // localized
params.unlinker.workerFailure(pageobj);
return;
}
pageobj.setEditSummary('حذف کاربردهای ' + image + ' که حذف شدهاست؛ دلیل حذف: ' + params.reason + Twinkle.getPref('deletionSummaryAd')); // localized
pageobj.setPageText(text);
pageobj.setCreateOption('nocreate');
pageobj.setMaxConflictRetries(10);
pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
}
};
})(jQuery);
// </nowiki>
/* ===== twinklebatchprotect.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklebatchprotect.js: Batch protect module (sysops only)
****************************************
* Mode of invocation: Tab ("P-batch")
* Active on: Existing project pages and user pages; existing and
* non-existing categories; Special:PrefixIndex
*/
Twinkle.batchprotect = function twinklebatchprotect() {
if (Morebits.userIsSysop && ((mw.config.get('wgArticleId') > 0 && (mw.config.get('wgNamespaceNumber') === 2 ||
mw.config.get('wgNamespaceNumber') === 4)) || mw.config.get('wgNamespaceNumber') === 14 ||
mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex')) {
Twinkle.addPortletLink(Twinkle.batchprotect.callback, 'محافظت دستهجمعی', 'tw-pbatch', 'محافظت صفحههایی که از این صفحه پیوند دارند'); // localized
}
};
Twinkle.batchprotect.unlinkCache = {};
Twinkle.batchprotect.callback = function twinklebatchprotectCallback() {
var Window = new Morebits.simpleWindow(600, 400);
Window.setTitle('محافظت دستهجمعی'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('سیاست حفاظت از صفحهها', 'وو:حفاظت'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#protect'); // localized
var form = new Morebits.quickForm(Twinkle.batchprotect.callback.evaluate);
form.append({
type: 'checkbox',
name: 'editmodify',
event: Twinkle.protect.formevents.editmodify,
list: [
{
label: 'تغییر حفاظت در برابر ویرایش', // localized
value: 'editmodify',
tooltip: 'فقط برای صفحههای از پیش موجود.', // localized
checked: true
}
]
});
var editlevel = form.append({
type: 'select',
name: 'editlevel',
label: 'حفاظت در برابر ویرایش:', // localized
event: Twinkle.protect.formevents.editlevel
});
editlevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
editlevel.append({
type: 'option',
label: 'تأییدشدهٔ خودکار', // localized
value: 'autoconfirmed'
});
editlevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed'
});
editlevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
editlevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
form.append({
type: 'select',
name: 'editexpiry',
label: 'زمان سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', selected: true, value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'سفارشی...', value: 'custom' } // localized
]
});
form.append({
type: 'checkbox',
name: 'movemodify',
event: Twinkle.protect.formevents.movemodify,
list: [
{
label: 'تغییر حفاظت در برابر انتقال', // localized
value: 'movemodify',
tooltip: 'فقط برای صفحههای از پیش موجود.', // localized
checked: true
}
]
});
var movelevel = form.append({
type: 'select',
name: 'movelevel',
label: 'حفاظت در برابر انتقال:', // localized
event: Twinkle.protect.formevents.movelevel
});
movelevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
movelevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed'
});
movelevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
movelevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
form.append({
type: 'select',
name: 'moveexpiry',
label: 'زمان سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', selected: true, value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'سفارشی...', value: 'custom' } // localized
]
});
form.append({
type: 'checkbox',
name: 'createmodify',
event: function twinklebatchprotectFormCreatemodifyEvent(e) {
e.target.form.createlevel.disabled = !e.target.checked;
e.target.form.createexpiry.disabled = !e.target.checked || (e.target.form.createlevel.value === 'all');
e.target.form.createlevel.style.color = e.target.form.createexpiry.style.color = e.target.checked ? '' : 'transparent';
},
list: [
{
label: 'تغییر حفاظت در برابر ایجاد', // localized
value: 'createmodify',
tooltip: 'فقط برای صفحههایی که موجود نیستند.', // localized
checked: true
}
]
});
var createlevel = form.append({
type: 'select',
name: 'createlevel',
label: 'حفاظت در برابر ایجاد:', // localized
event: Twinkle.protect.formevents.createlevel
});
createlevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
createlevel.append({
type: 'option',
label: 'تأییدشده خودکار', // localized
value: 'autoconfirmed'
});
createlevel.append({
type: 'option',
label: 'تأییدشده پایدار', // localized
value: 'extendedconfirmed'
});
createlevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
createlevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
form.append({
type: 'select',
name: 'createexpiry',
label: 'زمان سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', selected: true, value: 'indefinite' }, // localized
{ label: 'سفارشی...', value: 'custom' } // localized
]
});
form.append({
type: 'header',
label: '' // horizontal rule
});
form.append({
type: 'input',
name: 'reason',
label: 'دلیل: ', // localized
size: 60,
tooltip: 'برای سیاههٔ حفاظت و تاریخچهٔ صفحه.' // localized
});
var query = {
'action': 'query',
'prop': 'revisions|info',
'rvprop': 'size',
'inprop': 'protection'
};
if (mw.config.get('wgNamespaceNumber') === 14) { // categories
query.generator = 'categorymembers';
query.gcmtitle = mw.config.get('wgPageName');
query.gcmlimit = Twinkle.getPref('batchMax');
} else if (mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex') {
query.generator = 'allpages';
query.gapnamespace = mw.util.getParamValue('namespace') || $('select[name=namespace]').val();
query.gapprefix = mw.util.getParamValue('prefix') || $('input[name=prefix]').val();
query.gaplimit = Twinkle.getPref('batchMax');
} else {
query.generator = 'links';
query.titles = mw.config.get('wgPageName');
query.gpllimit = Twinkle.getPref('batchMax');
}
var statusdiv = document.createElement('div');
statusdiv.style.padding = '15px'; // just so it doesn't look broken
Window.setContent(statusdiv);
Morebits.status.init(statusdiv);
Window.display();
var statelem = new Morebits.status('گرفتن فهرست صفحهها'); // localized
var wikipedia_api = new Morebits.wiki.api('در حال بارگیری...', query, function(apiobj) { // localized
var xml = apiobj.responseXML;
var $pages = $(xml).find('page');
var list = [];
$pages.each(function(index, page) {
var $page = $(page);
var title = $page.attr('title');
var isRedir = $page.attr('redirect') === ''; // XXX ??
var missing = $page.attr('missing') === ''; // XXX ??
var size = $page.find('rev').attr('size');
var $editProt;
var metadata = [];
if (missing) {
metadata.push('صفحهٔ ناموجود'); // localized
$editProt = $page.find('pr[type="create"][level="sysop"]');
} else {
if (isRedir) {
metadata.push('تغییرمسیر'); // localized
}
metadata.push(mw.language.convertNumber(size) + ' بایت'); // localized
$editProt = $page.find('pr[type="edit"][level="sysop"]');
}
if ($editProt.length > 0) {
metadata.push('محافظت کامل' + (missing ? ' در برابر ایجاد' : '') + // localized
($editProt.attr('expiry') === 'infinity' ? ' بیپایان' : '، سرآمدن در ' + new Morebits.date($editProt.attr('expiry')).calendar('utc') + ' (UTC)')); // localized
}
list.push({ label: title + (metadata.length ? ' (' + metadata.join('; ') + ')' : ''), value: title, checked: true, style: $editProt.length > 0 ? 'color:red' : '' });
});
form.append({ type: 'header', label: 'صفحههایی که محافظت خواهند شد' }); // localized
form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', true);
}
});
form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', false);
}
});
form.append({
type: 'checkbox',
name: 'pages',
list: list
});
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Morebits.checkboxShiftClickSupport(Morebits.quickForm.getElements(result, 'pages'));
}, statelem);
wikipedia_api.post();
};
Twinkle.batchprotect.currentProtectCounter = 0;
Twinkle.batchprotect.currentprotector = 0;
Twinkle.batchprotect.callback.evaluate = function twinklebatchprotectCallbackEvaluate(event) {
Morebits.wiki.actionCompleted.notice = 'محافظت دستهجمعی کامل شد'; // localized
var form = event.target;
var numProtected = $(Morebits.quickForm.getElements(form, 'pages')).filter(function(index, element) {
return element.checked && element.nextElementSibling.style.color === 'red';
}).length;
if (numProtected > 0 && !confirm('شما در حال اعمال تغییرات بر روی' + mw.language.convertNumber(numProtected) + ' صفحهٔ کاملاً حفاظتشده هستید. آیا مطمئن هستید؟')) { // localized
return;
}
var pages = form.getChecked('pages');
var reason = form.reason.value;
var editmodify = form.editmodify.checked;
var editlevel = form.editlevel.value;
var editexpiry = form.editexpiry.value;
var movemodify = form.movemodify.checked;
var movelevel = form.movelevel.value;
var moveexpiry = form.moveexpiry.value;
var createmodify = form.createmodify.checked;
var createlevel = form.createlevel.value;
var createexpiry = form.createexpiry.value;
if (!reason) {
alert("باید دلیلی برای کارتان ارائه کنید!"); // localized
return;
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
if (!pages) {
Morebits.status.error('Error', 'چیزی برای حفاظت موجود نیست؛ در حال لغو کردن'); // localized
return;
}
var batchOperation = new Morebits.batchOperation('اعمال تنظیمات محافظت'); // localized
batchOperation.setOption('chunkSize', Twinkle.getPref('batchProtectChunks'));
batchOperation.setOption('preserveIndividualStatusLines', true);
batchOperation.setPageList(pages);
batchOperation.run(function(pageName) {
var query = {
'action': 'query',
'titles': pageName
};
var wikipedia_api = new Morebits.wiki.api('بررسی موجود بودن صفحه' + pageName + '', query, // localized
Twinkle.batchprotect.callbacks.main, null, batchOperation.workerFailure);
wikipedia_api.params = {
page: pageName,
reason: reason,
editmodify: editmodify,
editlevel: editlevel,
editexpiry: editexpiry,
movemodify: movemodify,
movelevel: movelevel,
moveexpiry: moveexpiry,
createmodify: createmodify,
createlevel: createlevel,
createexpiry: createexpiry,
batchOperation: batchOperation
};
wikipedia_api.post();
});
};
Twinkle.batchprotect.callbacks = {
main: function(apiobj) {
var xml = apiobj.responseXML;
var normal = $(xml).find('normalized n').attr('to');
if (normal) {
apiobj.params.page = normal;
}
var exists = $(xml).find('page').attr('missing') !== '';
var page = new Morebits.wiki.page(apiobj.params.page, 'محافظت ' + apiobj.params.page); // localized
var takenAction = false;
if (exists && apiobj.params.editmodify) {
page.setEditProtection(apiobj.params.editlevel, apiobj.params.editexpiry);
takenAction = true;
}
if (exists && apiobj.params.movemodify) {
page.setMoveProtection(apiobj.params.movelevel, apiobj.params.moveexpiry);
takenAction = true;
}
if (!exists && apiobj.params.createmodify) {
page.setCreateProtection(apiobj.params.createlevel, apiobj.params.createexpiry);
takenAction = true;
}
if (!takenAction) {
Morebits.status.warn('محافظت صفحهٔ ' + apiobj.params.page, 'از پیش وجود ' + (exists ? 'دارد' : 'ندارد') + '؛ چیزی برای انجام دادن باقی نماندهاست'); // localizing
apiobj.params.batchOperation.workerFailure(apiobj);
return;
}
page.setEditSummary(apiobj.params.reason);
page.protect(apiobj.params.batchOperation.workerSuccess, apiobj.params.batchOperation.workerFailure);
}
};
})(jQuery);
// </nowiki>
/* ===== twinklebatchundelete.js ===== */
// <nowiki>
(function($) {
/*
****************************************
*** twinklebatchundelete.js: Batch undelete module
****************************************
* Mode of invocation: Tab ("Und-batch")
* Active on: Existing user and project pages
*/
Twinkle.batchundelete = function twinklebatchundelete() {
if (!Morebits.userIsSysop || !mw.config.get('wgArticleId') || (
mw.config.get('wgNamespaceNumber') !== mw.config.get('wgNamespaceIds').user &&
mw.config.get('wgNamespaceNumber') !== mw.config.get('wgNamespaceIds').project)) {
return;
}
Twinkle.addPortletLink(Twinkle.batchundelete.callback, 'احیای دستهجمعی', 'tw-batch-undel', "احیای چندین صفحه به طور همزمان"); // localized
};
Twinkle.batchundelete.callback = function twinklebatchundeleteCallback() {
var Window = new Morebits.simpleWindow(600, 400);
Window.setScriptName('توینکل'); // localized
Window.setTitle('احیای دستهجمعی'); // localizde
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#batchundelete'); // localized
var form = new Morebits.quickForm(Twinkle.batchundelete.callback.evaluate);
form.append({
type: 'checkbox',
list: [
{
label: 'صفحههای بحث مرتبط با صفحههای احیا شده را در صورتی که وجود داشتهاند، احیا کن', // localized
name: 'undel_talk',
value: 'undel_talk',
checked: true
}
]
});
form.append({
type: 'input',
name: 'reason',
label: 'دلیل: ', // localized
size: 60
});
var statusdiv = document.createElement('div');
statusdiv.style.padding = '15px'; // just so it doesn't look broken
Window.setContent(statusdiv);
Morebits.status.init(statusdiv);
Window.display();
var query = {
'action': 'query',
'generator': 'links',
'prop': 'info',
'inprop': 'protection',
'titles': mw.config.get('wgPageName'),
'gpllimit': Twinkle.getPref('batchMax')
};
var statelem = new Morebits.status('تهیهٔ فهرست صفحهها'); // localized
var wikipedia_api = new Morebits.wiki.api('در حال بارگیری...', query, function(apiobj) { // localized
var xml = apiobj.responseXML;
var $pages = $(xml).find('page[missing]');
var list = [];
$pages.each(function(index, page) {
var $page = $(page);
var title = $page.attr('title');
var $editprot = $page.find('pr[type="create"][level="sysop"]');
var isProtected = $editprot.length > 0;
list.push({
label: title + (isProtected ? ' (محافظتشده در برابر ایجاد' +
($editprot.attr('expiry') === 'infinity' ? ' به صورت بیپایان' : '، انقضا در ' + new Morebits.date($editprot.attr('expiry')).calendar('utc') + ' (UTC)') + ')' : ''), // localized
value: title,
checked: true,
style: isProtected ? 'color:red' : ''
});
});
apiobj.params.form.append({ type: 'header', label: 'صفحهها برای احیا' }); // localized
apiobj.params.form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', true);
}
});
apiobj.params.form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', false);
}
});
apiobj.params.form.append({
type: 'checkbox',
name: 'pages',
list: list
});
apiobj.params.form.append({ type: 'submit' });
var result = apiobj.params.form.render();
apiobj.params.Window.setContent(result);
Morebits.checkboxShiftClickSupport(Morebits.quickForm.getElements(result, 'pages'));
}, statelem);
wikipedia_api.params = { form: form, Window: Window };
wikipedia_api.post();
};
Twinkle.batchundelete.callback.evaluate = function(event) {
Morebits.wiki.actionCompleted.notice = 'احیای دستهجمعی کامل شد'; // localized
var numProtected = $(Morebits.quickForm.getElements(event.target, 'pages')).filter(function(index, element) {
return element.checked && element.nextElementSibling.style.color === 'red';
}).length;
if (numProtected > 0 && !confirm('شما در حال احیای ' + numProtected + ' صفحه هستید که در برابر ایجاد محافظت شدهاند. آیا مطمئن هستید؟')) { // localized
return;
}
var pages = event.target.getChecked('pages');
var reason = event.target.reason.value;
var undel_talk = event.target.reason.value;
if (!reason) {
alert('باید دلیلی ارائه کنید!'); // localized
return;
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(event.target);
if (!pages) {
Morebits.status.error('Error', 'چیزی برای احیا موجود نیست، احیا متوقف شد'); // localized
return;
}
var pageUndeleter = new Morebits.batchOperation('Undeleting pages');
pageUndeleter.setOption('chunkSize', Twinkle.getPref('batchUndeleteChunks'));
pageUndeleter.setOption('preserveIndividualStatusLines', true);
pageUndeleter.setPageList(pages);
pageUndeleter.run(function(pageName) {
var params = {
page: pageName,
undel_talk: undel_talk,
reason: reason,
pageUndeleter: pageUndeleter
};
var wikipedia_page = new Morebits.wiki.page(pageName, 'احیای صفحهٔ ' + pageName); // localized
wikipedia_page.setCallbackParameters(params);
wikipedia_page.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
wikipedia_page.suppressProtectWarning();
wikipedia_page.setMaxRetries(3); // temporary increase from 2 to make batchundelete more likely to succeed [[phab:T222402]] #613
wikipedia_page.undeletePage(Twinkle.batchundelete.callbacks.doExtras, pageUndeleter.workerFailure);
});
};
Twinkle.batchundelete.callbacks = {
// this stupid parameter name is a temporary thing until I implement an overhaul
// of Morebits.wiki.* callback parameters
doExtras: function(thingWithParameters) {
var params = thingWithParameters.parent ? thingWithParameters.parent.getCallbackParameters() :
thingWithParameters.getCallbackParameters();
// the initial batch operation's job is to delete the page, and that has
// succeeded by now
params.pageUndeleter.workerSuccess(thingWithParameters);
var query, wikipedia_api;
if (params.undel_talk) {
var talkpagename = new mw.Title(params.page).getTalkPage().getPrefixedText();
if (talkpagename !== params.page) {
query = {
'action': 'query',
'prop': 'deletedrevisions',
'drvprop': 'ids',
'drvlimit': 1,
'titles': talkpagename
};
wikipedia_api = new Morebits.wiki.api('بررسی صفحهٔ بحث برای نسخههای حذفشده', query, Twinkle.batchundelete.callbacks.undeleteTalk); // localized
wikipedia_api.params = params;
wikipedia_api.params.talkPage = talkpagename;
wikipedia_api.post();
}
}
},
undeleteTalk: function(apiobj) {
var xml = apiobj.responseXML;
var exists = $(xml).find('page:not([missing])').length > 0;
var delrevs = $(xml).find('rev').attr('revid');
if (exists || !delrevs) {
// page exists or has no deleted revisions; forget about it
return;
}
var page = new Morebits.wiki.page(apiobj.params.talkPage, 'احیای صفحهٔ بحث ' + apiobj.params.page); // localized
page.setEditSummary('احیای [[راهنما:صفحه بحث|صفحهٔ بحث]] "' + apiobj.params.page + '"' + Twinkle.getPref('deletionSummaryAd')); // localized
page.undeletePage();
}
};
})(jQuery);
// </nowiki>
// </nowiki>
k6mld8gimvx5pq1qh2yad0f9tk03ij1
کاربر:Mamouri/twinkle-bundle.css
2
219482
908988
2026-07-30T15:58:18Z
Mamouri
34239
بهروزرسانی شیوهنامهٔ آزمایشی توینکل
908988
css
text/css
/**
* morebits.css
* ===========
* Styles to support morebits.js.
*
* The morebits library is maintained by the maintainers of Twinkle.
* For queries, suggestions, help, etc., head to [[WT:TW]].
* The latest development source is available at [https://github.com/azatoth/twinkle/blob/master/morebits.css].
*/
/* Morebits.status */
.morebits_status_status {
color: #4682B4;
}
.morebits_status_info {
color: #228B22;
}
.morebits_status_warn {
color: #FF4500;
}
.morebits_status_error {
color: #FF4500;
font-weight: bold;
}
/* Morebits.quickForm */
form.quickform
{
width: 96%;
vertical-align: middle;
margin: auto;
padding: .5em;
}
form.quickform *
{
font-family: sans-serif;
}
form.quickform fieldset
{
margin: .4em 0 1em;
}
form.quickform legend
{
color: #31628F;
font-weight: bold;
}
form.quickform input[type=text], form.quickform select
{
min-width: 15em;
font-size: 110%;
}
form.quickform select
{
border: 1px solid gray;
margin-left: .2em;
}
form.quickform input[type=checkbox],
form.quickform input[type=radio] {
height: 13px;
margin-bottom: 2px;
margin-top: 2px;
padding: 0;
width: 13px;
vertical-align: top;
}
form.quickform div {
line-height: 18px;
}
form.quickform h5
{
margin: .5em 0 0;
padding: .3em .2em .2em;
}
/* only give the top border to headers with something above them */
form.quickform div + h5, form.quickform div + div > h5, form.quickform h5 + h5
{
border-top: 1px solid #88A;
}
form.quickform textarea
{
width: 100%;
height: 4em;
font-size: 150%;
}
form.quickform input:disabled + label
{
color: gray;
}
form.quickform span.quickformDescription
{
font-style: italic;
}
form.quickform .quickformSubgroup
{
margin-bottom: .5em;
margin-left: 3em;
}
form.quickform .tooltipButtonContainer
{
position: relative;
width: 100%;
}
form.quickform .tooltipButton
{
color: blue;
font-weight: bold;
cursor: help;
padding: .2em;
}
/* Scrollbox styles, for use within Morebits.simpleWindow */
div.morebits-scrollbox
{
background: white;
border: 1px solid gray;
margin-bottom: .6em;
margin-top: .6em;
max-height: 20em;
overflow: auto;
padding: 6px 6px 0;
}
div.morebits-scrollbox > h5:first-child
{
border: none;
margin-top: 0;
padding-top: 0;
}
div.morebits-scrollbox > :last-child
{
margin-bottom: 6px;
}
/* Previewbox */
div.morebits-previewbox
{
background: white;
color: black;
border: 2px inset;
margin: .4em auto .2em;
padding: .2em .4em;
}
div.morebits-previewbox *:not(img)
{
vertical-align: baseline;
}
div.morebits-previewbox .mw-editsection
{
display: none;
}
/* Morebits.simpleWindow */
.morebits-dialog {
border: 1px #666 solid;
font: small sans-serif;
background-color: #F0F8FF;
background-image: none;
}
body.skin-monobook .morebits-dialog {
font-size: 125%;
}
body .ui-dialog.morebits-dialog .ui-dialog-titlebar {
height: 1em;
background-color: #BCCADF;
background-image: none;
font: bold 1em sans-serif;
overflow: hidden;
padding: .4em .3em .5em;
white-space: nowrap;
}
body.skin-monobook .morebits-dialog .ui-dialog-titlebar {
line-height: 1em;
}
.morebits-dialog-scriptname {
font-weight: normal;
}
.ui-dialog.morebits-dialog .ui-dialog-titlebar-close {
height: 100%;
right: 0;
top: auto;
width: 2em;
margin: -.5em -.15em 0;
}
.ui-dialog.morebits-dialog .ui-dialog-titlebar-close span {
margin: .33em;
}
.ui-dialog.morebits-dialog .morebits-dialog-content {
font-size: 88%; /* this just seems to be traditional - I don't quite see the point, in this day and age of huge monitors, etc. */
padding: 0;
}
body .ui-dialog.morebits-dialog .ui-dialog-buttonpane {
background-color: #BCCADF;
margin: 0;
min-height: .5em;
padding-left: 1.2em;
}
body .ui-dialog.morebits-dialog .ui-dialog-buttonpane button {
float: none;
margin: .2em 0 -.1em;
cursor: auto;
}
.morebits-dialog-footerlinks {
font-size: 90%;
float: right;
margin: .7em .4em 0 0;
}
body .ui-dialog.morebits-dialog .morebits-dialog-footerlinks a {
color: #3062AD;
}
.morebits-dialog-buttons[data-empty] + .morebits-dialog-footerlinks {
margin: .1em .4em -.2em 0;
}
.ui-icon {
vertical-align: -3px;
}
.ui-icon-inline {
display: inline-block;
}
/**
* Explicitly set width of TW menu so that we can use a hidden peer gadget
* to add space where the TW menu would go before it loads.
*/
.skin-vector .vector-menu-dropdown #p-twinkle {
width: 3.24em;
}
/* The additional box on user skin.js and twinklepreferences.js pages */
#twinkle-config-headerbox {
border: 1px #f60 solid;
background: #fed;
padding: 0.6em;
margin: 0.5em auto;
text-align: center;
}
/* twinkleoptions.js */
#twinkle-config-headerbox.config-twopt-box {
font-weight: bold;
width: 80%;
border-width: 2px;
}
/* skin-specific js */
#twinkle-config-headerbox.config-userskin-box {
width: 60%;
}
qte156pntnmglmh1yh32rcygkdg8xd1
کاربر:Mamouri/twinkle/Twinkle-2020.js
2
219483
908990
2026-07-30T16:03:48Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
908990
javascript
text/javascript
/**
* +-------------------------------------------------------------------------+
* | === WARNING: GLOBAL GADGET FILE === |
* | Changes to this page affect many users. |
* | Please discuss changes at [[WT:TW]] before editing. |
* +-------------------------------------------------------------------------+
*
* Imported from github [https://github.com/azatoth/twinkle].
* All changes should be made in the repository, otherwise they will be lost.
*
* ----------
*
* This is AzaToth's Twinkle, the popular script sidekick for newbies, admins, and
* every Wikipedian in between. Visit [[WP:TW]] for more information.
*/
// <nowiki>
/* global Morebits */
(function (window, document, $) { // Wrap with anonymous function
// Check if account is experienced enough to use Twinkle
if (!Morebits.userIsInGroup('autoconfirmed') && !Morebits.userIsInGroup('confirmed')) {
return;
}
var Twinkle = {};
window.Twinkle = Twinkle; // allow global access
// for use by custom modules (normally empty)
Twinkle.initCallbacks = [];
Twinkle.addInitCallback = function twinkleAddInitCallback(func) {
Twinkle.initCallbacks.push(func);
};
Twinkle.defaultConfig = {};
/**
* This holds the default set of preferences used by Twinkle.
* It is important that all new preferences added here, especially admin-only ones, are also added to
* |Twinkle.config.sections| in twinkleconfig.js, so they are configurable via the Twinkle preferences panel.
* For help on the actual preferences, see the comments in twinkleconfig.js.
*
* Formerly Twinkle.defaultConfig.twinkle and Twinkle.defaultConfig.friendly
*/
Twinkle.defaultConfig = {
// General
summaryAd: ' ([[ویکیواژه:توینکل|توینکل]])', // localized
deletionSummaryAd: ' ([[ویکیواژه:توینکل|توینکل]])', // localized
protectionSummaryAd: ' ([[ویکیواژه:توینکل|توینکل]])', // localized
userTalkPageMode: 'tab',
dialogLargeFont: false,
disabledModules: [],
disabledSysopModules: [],
// Block
defaultToPartialBlocks: false,
blankTalkpageOnIndefBlock: false,
// Fluff (revert and rollback)
autoMenuAfterRollback: false,
openTalkPage: [ 'agf', 'norm', 'vand' ],
openTalkPageOnAutoRevert: false,
rollbackInPlace: false,
markRevertedPagesAsMinor: [ 'vand' ],
watchRevertedPages: [ 'agf', 'norm', 'vand', 'torev' ],
offerReasonOnNormalRevert: true,
confirmOnFluff: false,
showRollbackLinks: [ 'diff', 'others' ],
// CSD
speedySelectionStyle: 'buttonClick',
watchSpeedyPages: [ "ع۳", "ع۵", "ع۱۰", "ع۱۱", "ع۱۲" ], // localized
markSpeedyPagesAsPatrolled: false,
// these next two should probably be identical by default
welcomeUserOnSpeedyDeletionNotification: [ "حذف سریع", "ع۱", "ع۲", "ع۳", "ع۴", "ع۶", "ع۱۰", "ع۱۱", "ع۱۲", "ع۱۳", "م۱", "م۲", "م۳", "م۵", "م۷", "م۹", "م۱۰", "م۱۱", "پ۱", "پ۲", "پ۳", "پ۷", "پ۹", "پ۱۰", "ک۳", "ک۵", "ت۲", "ت۳", "د۱", "د۲" ], // localized
notifyUserOnSpeedyDeletionNomination: [ "حذف سریع", "ع۱", "ع۲", "ع۳", "ع۴", "ع۶", "ع۱۰", "ع۱۱", "ع۱۲", "ع۱۳", "م۱", "م۲", "م۳", "م۵", "م۷", "م۹", "م۱۰", "م۱۱", "پ۱", "پ۲", "پ۳", "پ۷", "پ۹", "پ۱۰", "ک۳", "ک۵", "ت۲", "ت۳", "د۱", "د۲" ], // localized
warnUserOnSpeedyDelete: [ "حذف سریع", "ع۱", "ع۲", "ع۳", "ع۴", "ع۵", "ع۱۰", "ع۱۱", "ع۱۲", "م۱", "م۳", "م۷", "م۹", "م۱۰", "م۱۱", "پ۳", "پ۷", "پ۹", "ک۳", "ک۵", "ت۲", "د۱" ], // localized
promptForSpeedyDeletionSummary: [],
deleteTalkPageOnDelete: true,
deleteRedirectsOnDelete: true,
deleteSysopDefaultToDelete: false,
speedyWindowHeight: 500,
speedyWindowWidth: 800,
logSpeedyNominations: false,
speedyLogPageName: 'نامزدهای حذف سریع', // localized
noLogOnSpeedyNomination: [ 'ک۱' ], // localized
// Unlink
unlinkNamespaces: [ '0', '10', '100', '118' ],
// Warn
defaultWarningGroup: '1',
combinedSingletMenus: false,
showSharedIPNotice: true,
watchWarnings: true,
oldSelect: false,
customWarningList: [],
// Hidden preferences
revertMaxRevisions: 50,
autolevelStaleDays: 3, // Huggle is 3, CBNG is 2
batchMax: 5000,
batchdeleteChunks: 50,
batchProtectChunks: 50,
batchundeleteChunks: 50,
proddeleteChunks: 50,
// Formerly defaultConfig.friendly:
// Tag
groupByDefault: true,
watchTaggedPages: true,
watchMergeDiscussions: true,
markTaggedPagesAsMinor: false,
markTaggedPagesAsPatrolled: true,
tagArticleSortOrder: 'cat',
customTagList: [],
customFileTagList: [],
customRedirectTagList: [],
// Welcome
topWelcomes: false,
watchWelcomes: true,
welcomeHeading: 'Welcome',
insertHeadings: true,
insertUsername: true,
insertSignature: true, // sign welcome templates, where appropriate
quickWelcomeMode: 'norm',
quickWelcomeTemplate: 'welcome',
customWelcomeList: [],
customWelcomeSignature: true,
// Talkback
markTalkbackAsMinor: true,
insertTalkbackSignature: true, // always sign talkback templates
talkbackHeading: 'بازبحث ' + mw.config.get('wgUserName'), // localized
adminNoticeHeading: 'آگاهسازی', // localized
mailHeading: "نامه دارید!", // localized
// Shared
markSharedIPAsMinor: true
};
// now some skin dependent config.
switch (mw.config.get('skin')) {
case 'vector':
Twinkle.defaultConfig.portletArea = 'right-navigation';
Twinkle.defaultConfig.portletId = 'p-twinkle';
Twinkle.defaultConfig.portletName = 'توینکل'; // localized
Twinkle.defaultConfig.portletType = 'menu';
Twinkle.defaultConfig.portletNext = 'p-cactions'; // localized
break;
case 'timeless':
Twinkle.defaultConfig.portletArea = '#page-tools .sidebar-inner';
Twinkle.defaultConfig.portletId = 'p-twinkle';
Twinkle.defaultConfig.portletName = 'توینکل'; // localized
Twinkle.defaultConfig.portletType = null;
Twinkle.defaultConfig.portletNext = 'p-userpagetools';
break;
default:
Twinkle.defaultConfig.portletArea = null;
Twinkle.defaultConfig.portletId = 'p-cactions';
Twinkle.defaultConfig.portletName = null;
Twinkle.defaultConfig.portletType = null;
Twinkle.defaultConfig.portletNext = null;
}
Twinkle.getPref = function twinkleGetPref(name) {
if (typeof Twinkle.prefs === 'object' && Twinkle.prefs[name] !== undefined) {
return Twinkle.prefs[name];
}
// Old preferences format, used before twinkleoptions.js was a thing
if (typeof window.TwinkleConfig === 'object' && window.TwinkleConfig[name] !== undefined) {
return window.TwinkleConfig[name];
}
if (typeof window.FriendlyConfig === 'object' && window.FriendlyConfig[name] !== undefined) {
return window.FriendlyConfig[name];
}
return Twinkle.defaultConfig[name];
};
/**
* **************** Twinkle.addPortlet() ****************
*
* Adds a portlet menu to one of the navigation areas on the page.
* This is necessarily quite a hack since skins, navigation areas, and
* portlet menu types all work slightly different.
*
* Available navigation areas depend on the skin used.
* Vector:
* For each option, the outer div class contains "vector-menu", the inner div class is "vector-menu-content", and the ul is "vector-menu-content-list"
* "mw-panel", outer div class contains "vector-menu-portal". Existing portlets/elements: "p-logo", "p-navigation", "p-interaction", "p-tb", "p-coll-print_export"
* "left-navigation", outer div class contains "vector-menu-tabs" or "vector-menu-dropdown". Existing portlets: "p-namespaces", "p-variants" (menu)
* "right-navigation", outer div class contains "vector-menu-tabs" or "vector-menu-dropdown". Existing portlets: "p-views", "p-cactions" (menu), "p-search"
* Special layout of p-personal portlet (part of "head") through specialized styles.
* Monobook:
* "column-one", outer div class "portlet", inner div class "pBody". Existing portlets: "p-cactions", "p-personal", "p-logo", "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export"
* Special layout of p-cactions and p-personal through specialized styles.
* Modern:
* "mw_contentwrapper" (top nav), outer div class "portlet", inner div class "pBody". Existing portlets or elements: "p-cactions", "mw_content"
* "mw_portlets" (sidebar), outer div class "portlet", inner div class "pBody". Existing portlets: "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export"
*
* @param String navigation -- id of the target navigation area (skin dependant, on vector either of "left-navigation", "right-navigation", or "mw-panel")
* @param String id -- id of the portlet menu to create, preferably start with "p-".
* @param String text -- name of the portlet menu to create. Visibility depends on the class used.
* @param String type -- type of portlet. Currently only used for the vector non-sidebar portlets, pass "menu" to make this portlet a drop down menu.
* @param Node nextnodeid -- the id of the node before which the new item should be added, should be another item in the same list, or undefined to place it at the end.
*
* @return Node -- the DOM node of the new item (a DIV element) or null
*/
Twinkle.addPortlet = function(navigation, id, text, type, nextnodeid) {
// sanity checks, and get required DOM nodes
var root = document.getElementById(navigation) || document.querySelector(navigation);
if (!root) {
return null;
}
var item = document.getElementById(id);
if (item) {
if (item.parentNode && item.parentNode === root) {
return item;
}
return null;
}
var nextnode;
if (nextnodeid) {
nextnode = document.getElementById(nextnodeid);
}
// verify/normalize input
var skin = mw.config.get('skin');
if (skin !== 'vector' || (navigation !== 'left-navigation' && navigation !== 'right-navigation')) {
type = null; // menu supported only in vector's #left-navigation & #right-navigation
}
var outerDivClass, innerDivClass;
switch (skin) {
case 'vector':
// XXX: portal doesn't work
if (navigation !== 'portal' && navigation !== 'left-navigation' && navigation !== 'right-navigation') {
navigation = 'mw-panel';
}
outerDivClass = 'vector-menu vector-menu-' + (navigation === 'mw-panel' ? 'portal' : type === 'menu' ? 'dropdown' : 'tabs');
innerDivClass = 'vector-menu-content';
break;
case 'modern':
if (navigation !== 'mw_portlets' && navigation !== 'mw_contentwrapper') {
navigation = 'mw_portlets';
}
outerDivClass = 'portlet';
break;
case 'timeless':
outerDivClass = 'mw-portlet';
innerDivClass = 'mw-portlet-body';
break;
default:
navigation = 'column-one';
outerDivClass = 'portlet';
break;
}
// Build the DOM elements.
var outerDiv = document.createElement('nav');
outerDiv.setAttribute('aria-labelledby', id + '-label');
// Vector getting vector-menu-empty FIXME TODO
outerDiv.className = outerDivClass + ' emptyPortlet';
outerDiv.id = id;
if (nextnode && nextnode.parentNode === root) {
root.insertBefore(outerDiv, nextnode);
} else {
root.appendChild(outerDiv);
}
var h3 = document.createElement('h3');
h3.id = id + '-label';
var ul = document.createElement('ul');
if (skin === 'vector') {
// add invisible checkbox to keep menu open when clicked
// similar to the p-cactions ("More") menu
if (outerDivClass.indexOf('vector-menu-dropdown') !== -1) {
var chkbox = document.createElement('input');
chkbox.className = 'vectorMenuCheckbox vector-menu-checkbox'; // remove vectorMenuCheckbox after 1.35-wmf.37 goes live
chkbox.setAttribute('type', 'checkbox');
chkbox.setAttribute('aria-labelledby', id + '-label');
outerDiv.appendChild(chkbox);
var span = document.createElement('span');
span.appendChild(document.createTextNode(text));
h3.appendChild(span);
var a = document.createElement('a');
a.href = '#';
$(a).click(function(e) {
e.preventDefault();
});
h3.appendChild(a);
}
outerDiv.appendChild(h3);
ul.className = 'menu vector-menu-content-list'; // remove menu after 1.35-wmf.37 goes live
} else {
h3.appendChild(document.createTextNode(text));
outerDiv.appendChild(h3);
}
if (innerDivClass) {
var innerDiv = document.createElement('div');
innerDiv.className = innerDivClass;
innerDiv.appendChild(ul);
outerDiv.appendChild(innerDiv);
} else {
outerDiv.appendChild(ul);
}
return outerDiv;
};
/**
* **************** Twinkle.addPortletLink() ****************
* Builds a portlet menu if it doesn't exist yet, and add the portlet link.
* @param task: Either a URL for the portlet link or a function to execute.
*/
Twinkle.addPortletLink = function(task, text, id, tooltip) {
if (Twinkle.getPref('portletArea') !== null) {
Twinkle.addPortlet(Twinkle.getPref('portletArea'), Twinkle.getPref('portletId'), Twinkle.getPref('portletName'), Twinkle.getPref('portletType'), Twinkle.getPref('portletNext'));
}
var link = mw.util.addPortletLink(Twinkle.getPref('portletId'), typeof task === 'string' ? task : '#', text, id, tooltip);
$('.client-js .skin-vector #p-cactions').css('margin-right', 'initial');
if (typeof task === 'function') {
$(link).click(function (ev) {
task();
ev.preventDefault();
});
}
if ($.collapsibleTabs) {
$.collapsibleTabs.handleResize();
}
return link;
};
/**
* **************** General initialization code ****************
*/
var scriptpathbefore = mw.util.wikiScript('index') + '?title=',
scriptpathafter = '&action=raw&ctype=text/javascript&happy=yes';
// Retrieve the user's Twinkle preferences
$.ajax({
url: scriptpathbefore + 'User:' + encodeURIComponent(mw.config.get('wgUserName')) + '/twinkleoptions.js' + scriptpathafter,
dataType: 'text'
})
.fail(function () {
mw.notify('Could not load your Twinkle preferences', {type: 'error'});
})
.done(function (optionsText) {
// Quick pass if user has no options
if (optionsText === '') {
return;
}
// Twinkle options are basically a JSON object with some comments. Strip those:
optionsText = optionsText.replace(/(?:^(?:\/\/[^\n]*\n)*\n*|(?:\/\/[^\n]*(?:\n|$))*$)/g, '');
// First version of options had some boilerplate code to make it eval-able -- strip that too. This part may become obsolete down the line.
if (optionsText.lastIndexOf('window.Twinkle.prefs = ', 0) === 0) {
optionsText = optionsText.replace(/(?:^window.Twinkle.prefs = |;\n*$)/g, '');
}
try {
var options = JSON.parse(optionsText);
if (options) {
if (options.twinkle || options.friendly) { // Old preferences format
Twinkle.prefs = $.extend(options.twinkle, options.friendly);
} else {
Twinkle.prefs = options;
}
// v2 established after unification of Twinkle/Friendly objects
Twinkle.prefs.optionsVersion = Twinkle.prefs.optionsVersion || 1;
}
} catch (e) {
mw.notify('Could not parse your Twinkle preferences', {type: 'error'});
}
})
.always(function () {
$(Twinkle.load);
});
// Developers: you can import custom Twinkle modules here
// For example, mw.loader.load(scriptpathbefore + "User:UncleDouggie/morebits-test.js" + scriptpathafter);
Twinkle.load = function () {
// Don't activate on special pages other than those on the whitelist so that
// they load faster, especially the watchlist.
var specialPageWhitelist = [ 'Block', 'Contributions', 'Recentchanges', 'Recentchangeslinked' ]; // wgRelevantUserName defined for non-sysops on Special:Block
if (Morebits.userIsSysop) {
specialPageWhitelist = specialPageWhitelist.concat([ 'DeletedContributions', 'Prefixindex' ]);
}
if (mw.config.get('wgNamespaceNumber') === -1 &&
specialPageWhitelist.indexOf(mw.config.get('wgCanonicalSpecialPageName')) === -1) {
return;
}
// Prevent clickjacking
if (window.top !== window.self) {
return;
}
// Set custom Api-User-Agent header, for server-side logging purposes
Morebits.wiki.api.setApiUserAgent('Twinkle/2.0 (' + mw.config.get('wgDBname') + ')');
// Load all the modules in the order that the tabs should appear
var twinkleModules = [
// User/user talk-related
'warn', 'arv', 'block', 'welcome', 'shared', 'talkback',
// Deletion
'speedy', 'prod', 'deprod',
// Maintenance
'protect', 'tag',
// Misc. ones last
'diff', 'unlink', 'fluff', 'batchdelete', 'batchprotect', 'batchundelete'
];
// Don't load modules users have disabled
var disabledModules = Twinkle.getPref('disabledModules').concat(Twinkle.getPref('disabledSysopModules'));
twinkleModules.filter(function(mod) {
return disabledModules.indexOf(mod) === -1;
}).forEach(function(module) {
Twinkle[module]();
});
Twinkle.config.init(); // Can't turn off
// Run the initialization callbacks for any custom modules
Twinkle.initCallbacks.forEach(function (func) {
func();
});
Twinkle.addInitCallback = function (func) {
func();
};
// Increases text size in Twinkle dialogs, if so configured
if (Twinkle.getPref('dialogLargeFont')) {
mw.util.addCSS('.morebits-dialog-content, .morebits-dialog-footerlinks { font-size: 100% !important; } ' +
'.morebits-dialog input, .morebits-dialog select, .morebits-dialog-content button { font-size: inherit !important; }');
}
// Hide the lingering space if the TW menu is empty
if (mw.config.get('skin') === 'vector' && Twinkle.getPref('portletType') === 'menu' && $('#p-twinkle').length === 0) {
$('#p-cactions').css('margin-right', 'initial');
}
};
}(window, document, jQuery)); // End wrap with anonymous function
// </nowiki>
3pa3vo0szrjp5872hp8wlkndoakluyb
کاربر:Mamouri/twinkle/twinkleconfig-2020.js
2
219484
908991
2026-07-30T16:03:55Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
908991
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** twinkleconfig.js: Preferences module
****************************************
* Mode of invocation: Adds configuration form to Wikipedia:Twinkle/Preferences,
and adds an ad box to the top of user subpages belonging to the
currently logged-in user which end in '.js'
* Active on: What I just said. Yeah.
I, [[User:This, that and the other]], originally wrote this. If the code is misbehaving, or you have any
questions, don't hesitate to ask me. (This doesn't at all imply [[WP:OWN]]ership - it's just meant to
point you in the right direction.) -- TTO
*/
Twinkle.config = {};
Twinkle.config.watchlistEnums = { yes: 'به فهرست پیگیری اضافه کن', no: "به فهرست پیگیری اضافه نکن", 'default': 'از تنظیمات وبگاه تبعیت کن' }; // localized
Twinkle.config.commonSets = {
csdCriteria: {
'حذف سریع': 'حذف سریع',
'خرابکاری': 'خرابکاری',
'حذف شده': 'حذف شده',
'نقض حق تکثیر': 'نقض حق تکثیر',
'تکراری': 'تکراری',
'استفاده نشده': 'استفاده نشده',
'ویکیمدیا': 'ویکیمدیا',
'بحث یتیم': 'بحث یتیم'
},
csdCriteriaDisplayOrder: [
'حذف سریع',
'خرابکاری',
'حذف شده',
'نقض حق تکثیر',
'تکراری',
'استفاده نشده',
'ویکیمدیا',
'بحث یتیم'
],
csdCriteriaNotification: {
'حذف سریع': 'حذف سریع',
'خرابکاری': 'خرابکاری',
'حذف شده': 'حذف شده',
'نقض حق تکثیر': 'نقض حق تکثیر',
'تکراری': 'تکراری',
'استفاده نشده': 'استفاده نشده',
'ویکیمدیا': 'ویکیمدیا',
'بحث یتیم': 'بحث یتیم'
},
csdCriteriaNotificationDisplayOrder: [
'حذف سریع',
'خرابکاری',
'حذف شده',
'نقض حق تکثیر',
'تکراری',
'استفاده نشده',
'ویکیمدیا',
'بحث یتیم'
],
csdAndDICriteria: {
'حذف سریع': 'حذف سریع',
'خرابکاری': 'خرابکاری',
'حذف شده': 'حذف شده',
'نقض حق تکثیر': 'نقض حق تکثیر',
'تکراری': 'تکراری',
'استفاده نشده': 'استفاده نشده',
'ویکیمدیا': 'ویکیمدیا',
'بحث یتیم': 'بحث یتیم'
},
csdAndDICriteriaDisplayOrder: [
'حذف سریع',
'خرابکاری',
'حذف شده',
'نقض حق تکثیر',
'تکراری',
'استفاده نشده',
'ویکیمدیا',
'بحث یتیم'
],
namespacesNoSpecial: {
'0': 'مقاله', // localized
'1': 'بحث (مقاله)', // localized
'2': 'کاربر', // localized
'3': 'بحث کاربر', // localized
'4': 'ویکیواژه', // localized
'5': 'بحث ویکیواژه', // localized
'6': 'پرونده', // localized
'7': 'بحث پرونده', // localized
'8': 'مدیاویکی', // localized
'9': 'بحث مدیاویکی', // localized
'10': 'الگو', // localized
'11': 'بحث الگو', // localized
'12': 'راهنما', // localized
'13': 'بحث راهنما', // localized
'14': 'رده', // localized
'15': 'بحث رده', // localized
'100': 'درگاه', // localized
'101': 'بحث درگاه', // localized
'108': 'کتاب', // localized
'109': 'بحث کتاب', // localized
'118': 'پیشنویس', // localized
'119': 'بحث پیشنویس', // localized
// '710': 'TimedText', // localized
// '711': 'TimedText talk', // localized
'828': 'پودمان', // localized
'829': 'بحث پودمان' // localized
}
};
/**
* Section entry format:
*
* {
* title: <human-readable section title>,
* adminOnly: <true for admin-only sections>,
* hidden: <true for advanced preferences that rarely need to be changed - they can still be modified by manually editing twinkleoptions.js>,
* preferences: [
* {
* name: <TwinkleConfig property name>,
* label: <human-readable short description - used as a form label>,
* helptip: <(optional) human-readable text (using valid HTML) that complements the description, like limits, warnings, etc.>
* adminOnly: <true for admin-only preferences>,
* type: <string|boolean|integer|enum|set|customList> (customList stores an array of JSON objects { value, label }),
* enumValues: <for type = "enum": a JSON object where the keys are the internal names and the values are human-readable strings>,
* setValues: <for type = "set": a JSON object where the keys are the internal names and the values are human-readable strings>,
* setDisplayOrder: <(optional) for type = "set": an array containing the keys of setValues (as strings) in the order that they are displayed>,
* customListValueTitle: <for type = "customList": the heading for the left "value" column in the custom list editor>,
* customListLabelTitle: <for type = "customList": the heading for the right "label" column in the custom list editor>
* },
* . . .
* ]
* },
* . . .
*
*/
Twinkle.config.sections = [
{
title: 'عمومی', // localized
preferences: [
// TwinkleConfig.summaryAd (string)
// Text to be appended to the edit summary of edits made using Twinkle
{
name: 'summaryAd',
label: "پسوند تبلیغی جهت ضمیمهشدن به خلاصه ویرایشهای توینکل", // localized
helptip: 'خلاصهٔ پیشرفته باید با یک فاصله آغاز شده و تا حد امکان کوتاه نوشتهشود.', // localized (to Huji: Summary Ad را به خلاصهٔ پیشرفته ترجمه کردم، چون «تبلیغ در خلاصه ویرایش منطقی نبود؛ آیا خلاصهٔ پیشرفته ترجمهٔ دقیقی است؟)
type: 'string'
},
// TwinkleConfig.deletionSummaryAd (string)
// Text to be appended to the edit summary of deletions made using Twinkle
{
name: 'deletionSummaryAd',
label: 'خلاصهٔ مورد استفاده در خلاصه ویرایش حذف', // localized
helptip: 'معمولاً برابر با همان خلاصه ویرایش پیشرفته در بالا.', // localized
adminOnly: true,
type: 'string'
},
// TwinkleConfig.protectionSummaryAd (string)
// Text to be appended to the edit summary of page protections made using Twinkle
{
name: 'protectionSummaryAd',
label: 'خلاصهٔ پیشرفته برای استفاده در خلاصه ویرایش محافظت صفحه', // localized
helptip: 'معمولاً برابر با همان خلاصه ویرایش پیشرفته در بالا.', // localized
adminOnly: true,
type: 'string'
},
// TwinkleConfig.userTalkPageMode may take arguments:
// 'window': open a new window, remember the opened window
// 'tab': opens in a new tab, if possible.
// 'blank': force open in a new window, even if such a window exists
{
name: 'userTalkPageMode',
label: 'باز کردن بحث کاربر در ...', // localized
type: 'enum',
enumValues: { window: 'یک پنجره، بهجای بحث کاربران', tab: 'یک زبانهٔ جدید', blank: 'یک پنجرهٔ کاملاً جدید' } // localized
},
// TwinkleConfig.dialogLargeFont (boolean)
{
name: 'dialogLargeFont',
label: 'استفاده از متن بزرگتر در پیامهای توینکل', // localized
type: 'boolean'
},
// Twinkle.config.disabledModules (array)
{
name: 'disabledModules',
label: 'خاموشکردن پودمانهای انتخابشدهٔ توینکل', // localized
helptip: 'هر آنچه در اینجا انتخاب کنید، برای استفاده در دسترس نخواهد بود، پس با دقت عمل کنید. برای فعالکردن دوباره، گزینهها را غیرفعال کنید.', // localized
type: 'set',
setValues: { /*arv: 'ARV',*/ warn: 'هشدار', welcome: 'خوش آمدید', shared: 'نشانی آیپی مشترک', talkback: 'بازبحث', speedy: 'حذف سریع', prod: 'حذف زماندار', xfd: 'نظرخواهی حذف', image: 'تصویر', protect: 'محافظت (دمص)', tag: 'برچسب', diff: 'تفاوت', unlink: 'حذف پیوند', 'fluff': 'بازگردانی و واگردانی' } // localized
},
// Twinkle.config.disabledSysopModules (array)
{
name: 'disabledSysopModules',
label: 'خاموش کردن پودمانهای ویژهٔ مدیران', // localized
helptip: 'هر آنچه در اینجا انتخاب کنید، برای استفاده در دسترس نخواهد بود، پس با دقت عمل کنید. برای فعالکردن دوباره، گزینهها را غیرفعال کنید.', // localized
adminOnly: true,
type: 'set',
setValues: { block: 'بستن', deprod: 'مخالفت با حذف زماندار (مخزمان)', batchdelete: 'حذف دستهجمعی', batchprotect: 'محافظت دستهجمعی', batchundelete: 'احیای دستهجمعی' } // localized
}
]
},
/*
{
title: 'ARV',
preferences: [
{
name: 'spiWatchReport',
label: 'افزودن صفحههای گزارش زاپاسبازی به فهرست پیگیری', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
}
]
},
*/ // localized [removed]
{
title: 'بستن کاربر', // localized
adminOnly: true,
preferences: [
// TwinkleConfig.defaultToPartialBlocks (boolean)
// Whether to default partial blocks on or off
{
name: 'defaultToPartialBlocks',
label: 'به صورت پیشفرض در زمان بازکردن منوی بستن، قطع دسترسی موردی انتخاب شود', // localized
type: 'boolean'
},
// TwinkleConfig.blankTalkpageOnIndefBlock (boolean)
// if true, blank the talk page when issuing an indef block notice (per [[WP:UWUL#Indefinitely blocked users]])
{
name: 'blankTalkpageOnIndefBlock',
label: 'صفحه بحث را در زمان قطع دسترسی بیپایان پاک شود', // localized
helptip: 'برای اطلاعات بیشتر، <a href="' + mw.util.getUrl('en:Wikipedia:WikiProject_User_warnings/Usage_and_layout#Indefinitely_blocked_users') + '">WP:UWUL</a> را ببینید.', // localized
type: 'boolean'
}
]
},
{
title: 'حذف تصویر', // localized
preferences: [
// TwinkleConfig.notifyUserOnDeli (boolean)
// If the user should be notified after placing a file deletion tag
{
name: 'notifyUserOnDeli',
label: '"اطلاعرسانی به بارگذار پرونده" به صورت پیشفرض فعال باشد', // localized
type: 'boolean'
},
// TwinkleConfig.deliWatchPage (string)
// The watchlist setting of the page tagged for deletion. Either "yes", "no", or "default". Default is "default" (Duh).
{
name: 'deliWatchPage',
label: 'تصویر در زمان برچسبزدن به فهرست پیگیری اضافه شود', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
// TwinkleConfig.deliWatchUser (string)
// The watchlist setting of the user talk page if a notification is placed. Either "yes", "no", or "default". Default is "default" (Duh).
{
name: 'deliWatchUser',
label: 'صفحه بحث بارگذار پرونده در زمان اطلاعرسانی، به فهرست پیگیری اضافه شود', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
}
]
},
{
title: 'حذف زماندار', // localized
preferences: [
// TwinkleConfig.watchProdPages (boolean)
// If, when applying prod template to page, to watch the page
{
name: 'watchProdPages',
label: 'مقاله در زمان برچسبزدن به فهرست پیگیری اضافه شود', // localized
type: 'boolean'
},
// TwinkleConfig.markProdPagesAsPatrolled (boolean)
// If, when applying prod template to page, to mark the page as curated/patrolled (if the page was reached from NewPages)
{
name: 'markProdPagesAsPatrolled',
label: 'علامت زدن صفحه بهعنوان گشتخورده/بازبینیشده در زمان برچسب زدن (در صورت امکان)', // localized
helptip: 'از آنجا که این عمل بر خلاف اجماع برای بهترین راهکار است، در بیشتر موارد نباید این گزینه را انتخاب کرد', // localized
type: 'boolean'
},
// TwinkleConfig.prodReasonDefault (string)
// The prefilled PROD reason.
{
name: 'prodReasonDefault',
label: 'دلیل پیشفرض حذف زماندار', // localized
type: 'string'
},
{
name: 'logProdPages',
label: 'نگهداری سیاههای از همهٔ صفحههایی که نامزد حذف زماندار کردهاید در زیرصفحه کاربری', // localized
helptip: 'از آنجا که کاربران غیرمدیر به مشارکتهای حذفشدهٔ خود دسترسی ندارند، سیاههٔ نگهداریشده در زیرصفحهٔ کاربری میتواند برای پیگیری تمام صفحههایی که با استفاده از توینکل نامزد حذف زماندار کردهاید، مفید باشد.', // localized
type: 'boolean'
},
{
name: 'prodLogPageName',
label: 'نگهداری سیاههٔ حذف زماندار در این زیرصفحهٔ کاربری', // localized
helptip: 'نام یک زیرصفحه را در این جعبه وارد کنید. میتوانید در صفحهٔ کاربر:<i>نام کاربری</i>/<i>نام زیرصفحه</i> به سیاههٔ حذف زماندار خود دسترسی داشته باشید. این قابلیت تنها در صورتی کار میکند که گزینهٔ سیاههٔ حذف زماندار در زیرصفحه کاربری را فعال کرده باشید.', // localized
type: 'string'
}
]
},
{
title: 'برگرداندن و واگردانی', // twinklefluff module // localized
preferences: [
// TwinkleConfig.autoMenuAfterRollback (bool)
// Option to automatically open the warning menu if the user talk page is opened post-reversion
{
name: 'autoMenuAfterRollback',
label: 'باز کردن خودکار منوی هشدار توینکل در صفحهٔ بحث کاربر پس از بازگردانی با توینکل', // localized
helptip: 'تنها در صورتی کار میکند که گزینهٔ مرتبط در پایین را فعال کرده باشید.', // localized
type: 'boolean'
},
// TwinkleConfig.openTalkPage (array)
// What types of actions that should result in opening of talk page
{
name: 'openTalkPage',
label: 'باز کردن بحث کاربر پس از انجام این گونههای بازبینی', // localized
type: 'set',
setValues: { agf: 'برگردانی با فرض حسن نیت', norm: 'برگردانی عادی', vand: 'برگردانی خرابکاری', torev: '«برگرداندن این نسخه»' } // localized
},
// TwinkleConfig.openTalkPageOnAutoRevert (bool)
// Defines if talk page should be opened when calling revert from contribs or recent changes pages. If set to true, openTalkPage defines then if talk page will be opened.
{
name: 'openTalkPageOnAutoRevert',
label: 'باز کردن بحث کاربر بههنگام فراخواندن برگردانی از طریق مشارکتهای کاربر یا تغییرات اخیر', // localized
helptip: 'برای اینکه این قابلیت کار کند، در صورت فعال بودن این گزینه باید گزینههای مورد نظر در تنظیمات قبلی نیز فعال شده باشند.', // localized
type: 'boolean'
},
// TwinkleConfig.rollbackInPlace (bool)
//
{
name: 'rollbackInPlace',
label: "جلوگیری از بارگیری مجدد صفحه در زمان برگردانی از طریق مشارکتهای کاربر یا تغییرات اخیر", // localized
helptip: "در صورت فعال بودن این گزینه، توینکل صفحهٔ مشارکتهای کاربر یا تغییرات اخیر را پس از برگردانی مجدداً بارگیری نخواهد کرد. این به شما این امکان را میدهد که همزمان بیش از یک ویرایش را برگردانی کنید.", // localized
type: 'boolean'
},
// TwinkleConfig.markRevertedPagesAsMinor (array)
// What types of actions that should result in marking edit as minor
{
name: 'markRevertedPagesAsMinor',
label: 'علامتزدن این گونههای بازبینی بهعنوان ویرایش جزئی',
type: 'set',
setValues: { agf: 'برگردانی با فرض حسن نیت', norm: 'برگردانی عادی', vand: 'برگردانی خرابکاری', torev: '«برگرداندن این نسخه»' }
},
// TwinkleConfig.watchRevertedPages (array)
// What types of actions that should result in forced addition to watchlist
{
name: 'watchRevertedPages',
label: 'برای این انواع واگردانی، صفحه را به فهرست پیگیری بیفزا', // localized
type: 'set',
setValues: { agf: 'برگردانی با فرض حسن نیت', norm: 'برگردانی عادی', vand: 'برگردانی خرابکاری', torev: '«برگرداندن این نسخه»' } // localized
},
// TwinkleConfig.offerReasonOnNormalRevert (boolean)
// If to offer a prompt for extra summary reason for normal reverts, default to true
{
name: 'offerReasonOnNormalRevert',
label: 'درخواست دلیل برای برگردانیهای عادی', // localized
helptip: 'برگردانیهای «عادی» آنهایی هستند که از طریق پیوند [برگردانی] میانی فراخوانده شدهاند.', // localized
type: 'boolean'
},
{
name: 'confirmOnFluff',
label: 'قبل از بازگرداندن پیغام تایید نمایش داده شود', // localized
helptip: 'برای کاربرانی که از دستگاههای لمسی یا قلمی استفاده میکنند، و افرادی که بهطور مزمن، مردد هستند.', // localized
type: 'boolean'
},
// TwinkleConfig.showRollbackLinks (array)
// Where Twinkle should show rollback links:
// diff, others, mine, contribs, history, recent
// Note from TTO: |contribs| seems to be equal to |others| + |mine|, i.e. redundant, so I left it out heres
{
name: 'showRollbackLinks',
label: 'نمایش پیوندهای برگردانی در این صفحهها', // localized
type: 'set',
setValues: { diff: 'صفحههای تفاوت', others: 'صفحههای مشارکتهای کاربران دیگر', mine: 'صفحهٔ مشارکتهای من', recent: 'تغییرات اخیر و صفحههای ویژهٔ تغییرات مرتبط', history: 'صفحههای تاریخچه' } // localized
}
]
},
{
title: 'برچسب آیپی مشترک', // localized
preferences: [
{
name: 'markSharedIPAsMinor',
label: 'علامتگذاری برچسبزدن آیپی مشترک بهعنوان ویرایش جزئی', // localized
type: 'boolean'
}
]
},
{
title: 'حذف سریع', // localized
preferences: [
{
name: 'speedySelectionStyle',
label: 'چه زمانی صفحه حذف شود یا برچسب زده شود؟', // localized
type: 'enum',
enumValues: { 'buttonClick': 'وقتی روی دکمهٔ «ثبت» کلیک کردم', 'radioClick': 'به محض کلیک کردن روی گزینه' } // localized
},
// TwinkleConfig.watchSpeedyPages (array)
// Whether to add speedy tagged or deleted pages to watchlist
{
name: 'watchSpeedyPages',
label: 'برای این معیارها، صفحه را به فهرست پیگیری بیفزا', // localized
type: 'set',
setValues: Twinkle.config.commonSets.csdCriteria,
setDisplayOrder: Twinkle.config.commonSets.csdCriteriaDisplayOrder
},
// TwinkleConfig.markSpeedyPagesAsPatrolled (boolean)
// If, when applying speedy template to page, to mark the page as triaged/patrolled (if the page was reached from NewPages)
{
name: 'markSpeedyPagesAsPatrolled',
label: 'در زمان نصب برچسب، صفحه را در صورت امکان گشت بزن یا بازبینی کن', // localized
helptip: 'این گزینه احتمالاً نباید استفاده شود چون برای آن اجماع وجود ندارد', // localized
type: 'boolean'
},
// TwinkleConfig.welcomeUserOnSpeedyDeletionNotification (array of strings)
// On what types of speedy deletion notifications shall the user be welcomed
// with a "firstarticle" notice if their talk page has not yet been created.
{
name: 'welcomeUserOnSpeedyDeletionNotification',
label: 'وقتی کاربر را از برچسبهای مربوط به این معیارها آگاه میکنی به او خوشامد هم بگو', // localized
helptip: 'پیام خوشامد فقط وقتی افزوده میشود که صفحهٔ بحث کاربر وجود نداشته باشد. الگوی {{نخستین نوشتار}} برای این منظور به کار خواهد رفت.', // localized
type: 'set',
setValues: Twinkle.config.commonSets.csdCriteriaNotification,
setDisplayOrder: Twinkle.config.commonSets.csdCriteriaNotificationDisplayOrder
},
// TwinkleConfig.notifyUserOnSpeedyDeletionNomination (array)
// What types of actions should result in the author of the page being notified of nomination
{
name: 'notifyUserOnSpeedyDeletionNomination',
label: 'برای این معیارها، سازندهٔ صفحه را آگاه کن', // localized
helptip: 'وقتی گزینهٔ آگاه کردن سازندهٔ صفحه را انتخاب میکند، این آگاهسازی فقط برای معیارهای زیر رخ خواهد داد.', // localized
type: 'set',
setValues: Twinkle.config.commonSets.csdCriteriaNotification,
setDisplayOrder: Twinkle.config.commonSets.csdCriteriaNotificationDisplayOrder
},
// TwinkleConfig.warnUserOnSpeedyDelete (array)
// What types of actions should result in the author of the page being notified of speedy deletion (admin only)
{
name: 'warnUserOnSpeedyDelete',
label: 'آگاهسازی سازندهٔ صفحه در زمان حذف با این معیارها', // localized
helptip: 'حتی در صورتی که در صفحهٔ معیارهای حذف سریع آگاهسازی را فعال کنید، این آگاهسازی تنها برای معیارهایی که در اینجا انتخاب شدهاند انجام خواهد شد.', // localized
adminOnly: true,
type: 'set',
setValues: Twinkle.config.commonSets.csdCriteriaNotification,
setDisplayOrder: Twinkle.config.commonSets.csdCriteriaNotificationDisplayOrder
},
// TwinkleConfig.promptForSpeedyDeletionSummary (array of strings)
{
name: 'promptForSpeedyDeletionSummary',
label: 'فراهم کردن امکان ویرایش خلاصهٔ حذف در صورت حذف با این معیارها', // localized
adminOnly: true,
type: 'set',
setValues: Twinkle.config.commonSets.csdAndDICriteria,
setDisplayOrder: Twinkle.config.commonSets.csdAndDICriteriaDisplayOrder
},
// TwinkleConfig.deleteTalkPageOnDelete (boolean)
// If talk page if exists should also be deleted (CSD G8) when spedying a page (admin only)
{
name: 'deleteTalkPageOnDelete',
label: 'انتخاب گزینهٔ «صفحهٔ بحث نیز حذف شود» بهطور پیشفرض', // localized
adminOnly: true,
type: 'boolean'
},
{
name: 'deleteRedirectsOnDelete',
label: 'انتخاب گزینهٔ «تغییرمسیرها نیز حذف شوند» بهطور پیشفرض', // localized
adminOnly: true,
type: 'boolean'
},
// TwinkleConfig.deleteSysopDefaultToDelete (boolean)
// Make the CSD screen default to "delete" instead of "tag" (admin only)
{
name: 'deleteSysopDefaultToDelete',
label: 'فعالسازی پیشفرض حذف بیدرنگ بهجای قرار دادن برچسب حذف سریع', // localized
helptip: 'در صورت از پیش موجود بودن برچسب حذف سریع، جالت پیشفرض توینکل همیشه حالت «حذف» خواهد بود', // localized
adminOnly: true,
type: 'boolean'
},
// TwinkleConfig.speedyWindowWidth (integer)
// Defines the width of the Twinkle SD window in pixels
{
name: 'speedyWindowWidth',
label: 'عرض پنجرهٔ حذف سریع (به پیکسل)', // localized
type: 'integer'
},
// TwinkleConfig.speedyWindowWidth (integer)
// Defines the width of the Twinkle SD window in pixels
{
name: 'speedyWindowHeight',
label: 'ارتفاع پنجرهٔ حذف سریع (به پیکسل)', // localized
helptip: 'اگر یک نمایشگر بزرگ دارید، ممکن است بخواهید این اندازه را افزایش دهید.', // localized
type: 'integer'
},
{
name: 'logSpeedyNominations',
label: 'نگهداری یک سیاهه از نامزدیها برای حذف سریع در زیرصفحهٔ کاربری', // localized
helptip: 'از آنجا که کاربران غیرمدیر به مشارکتهای حذفشدهٔ خود دسترسی ندارند، سیاههٔ نگهداریشده در زیرصفحهٔ کاربری میتواند برای پیگیری تمام صفحههایی که با استفاده از توینکل نامزد حذف سریع کردهاید، مفید باشد. پروندههایی که با معیارهای حذف پرونده برچسب خوردهاند نیز به این سیاهه افزوده میشوند.', // localized
type: 'boolean'
},
{
name: 'speedyLogPageName',
label: 'نگهداری سیاههٔ حذف سریع در این زیرصفحهٔ کاربری', // localized
helptip: 'نام یک زیرصفحه را در این جعبه وارد کنید. میتوانید در صفحهٔ کاربر:<i>نام کربری</i>/<i>نام زیرصفحه</i> به سیاههٔ حذف سریع خود دسترسی داشته باشید. این قابلیت تنها در صورتی کار میکند که گزینهٔ سیاههٔ حذف سریع در زیرصفحه کاربری را فعال کرده باشید.', // localized
type: 'string'
},
{
name: 'noLogOnSpeedyNomination',
label: 'عدم افزودن صفحه به سیاههٔ حذف در فضای کاربری در زمان استفاده از این معیارها',
type: 'set',
setValues: Twinkle.config.commonSets.csdAndDICriteria,
setDisplayOrder: Twinkle.config.commonSets.csdAndDICriteriaDisplayOrder
}
]
},
{
title: 'برچسب', // localized
preferences: [
{
name: 'watchTaggedPages',
label: 'در زمان افزودن برچسب، صفحه را به فهرست پیگیری بیفزا', // localized
type: 'boolean'
},
{
name: 'watchMergeDiscussions',
label: 'افزودن صفحههای بحث به فهرست پیگیری در زمان آغاز نظرخواهی برای ادغام', // localized
type: 'boolean'
},
{
name: 'markTaggedPagesAsMinor',
label: 'علامتزدن ویرایشها برای افزودن برچسب بهعنوان ویرایش جزئی', // localized
type: 'boolean'
},
{
name: 'markTaggedPagesAsPatrolled',
label: 'انتخاب گزینهٔ «علامتگذاری صفحه بهعنوان گشتخورده/بازبینیشده» بهصورت پیشفرض', // localized
type: 'boolean'
},
{
name: 'groupByDefault',
label: 'انتخاب گزینهٔ «گروهبندی در {{مشکلات متعدد}}» بهصورت پیشفرض', // localized
type: 'boolean'
},
{
name: 'tagArticleSortOrder',
label: 'حالت پیشفرض برای مرتبسازی برچسبهای مقالهها', // localized
type: 'enum',
enumValues: { 'cat': 'بر پایه رده', 'alpha': 'به ترتیب الفبا' } // localized
},
{
name: 'customTagList',
label: 'برچسبهای نگهداری سفارشی مقاله/پیشنویس جهت نمایش', // localized
helptip: "این برچسبها بهعنوان گزینههای بیشتر در پایین فهرست برچسبها نمایش مییابند. بهعنوان مثال، میتوانید برچسبهای نگهداری اضافهای که هنوز به برچسبهای پیشفرض توینکل اضافه نشدهاند را بیافزایید.", // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در کادر برچسب' // localized
},
{
name: 'customFileTagList',
label: 'برچسبهای سفارشی نگهداری پرونده جهت نمایش', // localized
helptip: 'برچسبهای بیشتری که میخواهید برای پروندهها بیافزایید.', // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در کادر برچسب' // localized
},
{
name: 'customRedirectTagList',
label: 'برچسبهای سفارشی ردهٔ تغییرمسیر جهت نمایش', // localized
helptip: 'برچسبهای بیشتری که میخواهید برای تغییرمسیرها بیافزایید.', // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در کادر برچسب' // localized
}
]
},
{
title: 'گفتگو', // localized
preferences: [
{
name: 'markTalkbackAsMinor',
label: 'علامتگذاری بازبحثها بهعنوان ویرایش جزئی', // localized
type: 'boolean'
},
{
name: 'insertTalkbackSignature',
label: 'قرار دادن امضا در درون بازبحثها', // localized
type: 'boolean'
},
{
name: 'talkbackHeading',
label: 'عنوان بخش برای استفاده در بازبحثها', // localized
type: 'string'
},
{
name: 'adminNoticeHeading',
label: "عنوان بخش برای استفاده در گزارشهای ارسالشده در تابلوی اعلانات مدیران", // localized
helptip: 'تنها مرتبط با وو:تام.', // localized
type: 'string'
},
{
name: 'mailHeading',
label: "عنوان بخش برای استفاده در پیامهای \«ایمیل دارید\»", // localized
type: 'string'
}
]
},
{
title: 'زدودن پیوند', // localized
preferences: [
// TwinkleConfig.unlinkNamespaces (array)
// In what namespaces unlink should happen, default in 0 (article), 10 (template), 100 (portal), and 118 (draft)
{
name: 'unlinkNamespaces',
label: 'پیوندها را از صفحههایی که در این فضاهای نام هستند بزدا', // localized
helptip: 'از انتخاب فضاهای بحث پرهیز کنید، چون توینکل ممکن است پیوند به بایگانیهای بحث را حذف کند که کاری است به شدت نکوهش شده.', // localized
type: 'set',
setValues: Twinkle.config.commonSets.namespacesNoSpecial
}
]
},
{
title: 'هشدار به کاربر', // localized
preferences: [
// TwinkleConfig.defaultWarningGroup (int)
// Which level warning should be the default selected group, default is 1
{
name: 'defaultWarningGroup',
label: 'سطح پیشفرض هشدارها', // localized
type: 'enum',
enumValues: {
'1': 'سطح ۱', // localized
'2': 'سطح ۲', // localized
'3': 'سطح ۳', // localized
'4': 'سطح ۴', // localized
'5': 'سطح ۵ (فوری)', // localized
'6': 'اعلان راجع به یک مشکل واحد', // localized
'7': 'هشدار راجع به یک مشکل واحد', // localized
// 8 was used for block templates before #260
'9': 'هشدار سفارشی', // localized
'10': 'تمام الگوهای هشدار', // localized
'11': 'انتخاب خودکار (سطح ۱-۴)' // localized
}
},
// TwinkleConfig.combinedSingletMenus (boolean)
// if true, show one menu with both single-issue notices and warnings instead of two separately
{
name: 'combinedSingletMenus',
label: 'جایگزینی دو منوی جداگانه برای تکاشکال در یک منوی ترکیبشده', // localized
helptip: 'انتخاب اعلانهای تکاشکال یا هشدارهای تکاشکال بهعنوان گزینهٔ پیشفرض شما، در صورت فعال بودن، این را به انتخاب پیشفرض شما تبدیل خواهد کرد.', // localized
type: 'boolean'
},
// TwinkleConfig.showSharedIPNotice may take arguments:
// true: to show shared ip notice if an IP address
// false: to not print the notice
{
name: 'showSharedIPNotice',
label: 'افزودن اعلان اضافی در صفحههای بحث آیپی مشترک', // localized
helptip: 'اعلان مورد استفاده، {{آگاهی آیپی مشترک}} است', // localized
type: 'boolean'
},
// TwinkleConfig.watchWarnings (boolean)
// if true, watch the page which has been dispatched an warning or notice, if false, default applies
{
name: 'watchWarnings',
label: 'افزودن بحث کاربری به فهرست پیگیری در زمان ارسال اعلان', // localized
type: 'boolean'
},
// TwinkleConfig.oldSelect (boolean)
// if true, use the native select menu rather the select2-based one
{
name: 'oldSelect',
label: 'استفاده از منوی انتخاب کلاسیک غیر قابل جستجو', // localized
type: 'boolean'
},
{
name: 'customWarningList',
label: 'الگوهای هشدار سفارشی برای نمایش', // localized
helptip: 'میتوانید الگوهای شخصی یا زیرصفحههای کاربری را بیافزایید. هشدارهای سفارشی در ردهٔ «هشدارهای سفارشی» در داخل کادر جعبهٔ هشدار نمایش مییابند.', // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در فهرست هشدار (برای خلاصه ویرایش نیز استفاده میشود)' // localized
}
]
},
{
title: 'خوشامدگویی', // localized
preferences: [
{
name: 'topWelcomes',
label: 'قرار دادن خوشامدهای در بالای محتوای موجود در صفحههای بحث کاربران', // localized
type: 'boolean'
},
{
name: 'watchWelcomes',
label: 'افزودن صفحههای بحث کاربران به فهرست پیگیری در زمان خوشامدگویی', // localized
helptip: 'انجام این کار، تجربهٔ شخصی شما از خوشامدگویی به یک کاربر را بهبود میبخشد - شما قادر خواهید بود تا واکنش کاربر بهعنوان یک تازهکار را ببینید و احتمالاً به او کمک کنید.', // localized
type: 'boolean'
},
{
name: 'insertUsername',
label: 'افزودن نام کربری شما به الگو (در صورت امکان)', // localized
helptip: "برخی الگوهای خوشامد دارای جملهٔ اولیهای مانند \«سلام، من <username> هستم. خوش آمدید\" یا مشابه آن هستند. اگر این گزینه را غیرفعال کنید، این الگوها نام کاربری شما را بهاین نحو نمایش نخواهند داد.", // localized
type: 'boolean'
},
{
name: 'quickWelcomeMode',
label: 'عملکرد پیوند «خوشامد» در صفحهٔ تفاوت', // localized
helptip: 'اگر حالت خوشامدگویی خودکار را انتخاب کنید، الگویی که در زیر مشخص کردهاید مورد استفاده قرار خواهد گرفت.', // localized
type: 'enum',
enumValues: { auto: 'خوشامدگویی خودکار', norm: 'الزام برای انتخاب یک الگو' } // localized
},
{
name: 'quickWelcomeTemplate',
label: 'الگوی مورد استفاده در زمان خوشامدگویی خودکار', // localized
helptip: 'نام یک الگوی خواشمد را بدون استفاده از آکولاد وارد کنید. پیوندی به مقالهٔ مورد نظر اضافه خواهد شد.', // localized
type: 'string'
},
{
name: 'customWelcomeList',
label: 'الگوهای خوشامد سفارشی برای نمایش', // localized
helptip: "میتوانید الگوهای خوشامد یا زیرصفحههای کاربری که الگوی خوشامدگویی هستند را اضافه کنید (همراه با پیشوند \"کاربر:\"). فراموش نکنید که این الگوها در صفحههای بحث کاربران جانشانی خواهند شد.", // localized
type: 'customList',
customListValueTitle: 'نام الگو (بدون آکولاد)', // localized
customListLabelTitle: 'متنی برای نمایش در کادر خوشامد' // localized
},
{
name: 'customWelcomeSignature',
label: 'امضای خودکار الگوهای خوشامد سفارشی', // localized
helptip: 'در صورتی که الگوهای خوشامد سفارشی شما شامل امضای یکپارچه در داخل الگو هستند، این گزینه را غیرفعال کنید.', // localized
type: 'boolean'
}
]
},
{
title: 'نظرخواهی برای حذف', // localized
preferences: [
{
name: 'logXfdNominations',
label: 'نگهداری یک سیاهه از نامزدیها در نظرخواهی برای حذف (نبح) توسط شما در زیرصفحهٔ کاربری', // localized
helptip: 'سیاههٔ نگهداریشده در زیرصفحهٔ کاربری میتواند برای پیگیری تمام صفحههایی که با استفاده از توینکل نامزد نظرخواهی برای حذف کردهاید، مفید باشد.', // localized
type: 'boolean'
},
{
name: 'xfdLogPageName',
label: 'نگهداری سیاههٔ نظرخواهی برای حذف در این زیرصفحهٔ کاربری', // localized
helptip: 'نام یک زیرصفحه را در این جعبه وارد کنید. میتوانید در صفحهٔ کاربر:<i>نام کربری</i>/<i>نام زیرصفحه</i> به سیاههٔ نظرخواهی برای حذف خود دسترسی داشته باشید. این قابلیت تنها در صورتی کار میکند که گزینهٔ سیاههٔ نظرخواهی برای حذف در زیرصفحه کاربری را فعال کرده باشید.', // localized
type: 'string'
},
{
name: 'noLogOnXfdNomination',
label: 'برای نامزدیهای حذف در این دسته، سیاههای در زیرصفحهٔ کاربریام ایجاد نکن', // localized
type: 'set',
setValues: { afd: 'نبح'/*, tfd: 'TfD', ffd: 'FfD', cfd: 'CfD', cfds: 'CfD/S', mfd: 'MfD', rfd: 'RfD', rm: 'RM'*/ } // localized
},
// TwinkleConfig.xfdWatchPage (string)
// The watchlist setting of the page being nominated for XfD. Either "yes" (add to watchlist), "no" (don't
// add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
{
name: 'xfdWatchPage',
label: 'صفحهٔ نامزد حذف را به فهرست پیگیری اضافه کن', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
// TwinkleConfig.xfdWatchDiscussion (string)
// The watchlist setting of the newly created XfD page (for those processes that create discussion pages for each nomination),
// or the list page for the other processes.
// Either "yes" (add to watchlist), "no" (don't add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
{
name: 'xfdWatchDiscussion',
label: 'صفحهٔ نظرخواهی را به فهرست پیگیری اضافه کن', // localized
helptip: 'این گزینه مربوط به صفحهای است که در آن بحث راجع به حذف صورت میگیرد، یعنی صفحهٔ نظرخواهی', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
// TwinkleConfig.xfdWatchList (string)
// The watchlist setting of the XfD list page, *if* the discussion is on a separate page. Either "yes" (add to watchlist), "no" (don't
// add to watchlist), or "default" (use setting from preferences). Default is "no" (Hehe. Seriously though, who wants to watch it?
// Sorry in advance for any false positives.).
/*
{
name: 'xfdWatchList',
label: 'افزودن صفحهٔ سیاهه/فهرست روزانه به فهرست پیگیری (در صورت امکان)', // localized
helptip: 'این گزینه تنها در مورد وو:نبح اعمال خواهد شد که بحثهای آن در یک سیاههٔ روزانه یا صفحهٔ اصلی نبح تراگنجایش میشوند.', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
*/ // localized [removed]
// TwinkleConfig.xfdWatchUser (string)
// The watchlist setting of the user talk page if they receive a notification. Either "yes" (add to watchlist), "no" (don't
// add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
{
name: 'xfdWatchUser',
label: 'صفحه بحث کاربر یا سازندهٔ اولیهٔ صفحه را در هنگام اطلاعرسانی به فهرست پیگیری اضافه کن', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
// TwinkleConfig.xfdWatchRelated (string)
// The watchlist setting of the target of a redirect being nominated for RfD. Either "yes" (add to watchlist), "no" (don't
// add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
{
name: 'xfdWatchRelated',
label: "در زمان اطلاعرسانی، صفحه هدف تغییرمسیر را به فهرست پیگیری اضافه کن", // localized
helptip: 'این فقط برای زمانی کاربرد دارد که شما در صفحه بحث هدف تغییرمسیر اطلاعرسانی کردهاید.', // localized
type: 'enum',
enumValues: Twinkle.config.watchlistEnums
},
{
name: 'markXfdPagesAsPatrolled',
label: 'در صورت امکان صفحه را در زمان ایجاد نظرخواهی برای حذف گشت بزن', // localized
type: 'boolean'
}
]
},
{
title: 'پنهان', // localized
hidden: true,
preferences: [
// twinkle.js: portlet setup
{
name: 'portletArea',
type: 'string'
},
{
name: 'portletId',
type: 'string'
},
{
name: 'portletName',
type: 'string'
},
{
name: 'portletType',
type: 'string'
},
{
name: 'portletNext',
type: 'string'
},
// twinklefluff.js: defines how many revision to query maximum, maximum possible is 50, default is 50
{
name: 'revertMaxRevisions',
type: 'integer'
},
// twinklewarn.js: When using the autolevel select option, how many days makes a prior warning stale
// Huggle is three days ([[Special:Diff/918980316]] and [[Special:Diff/919417999]]) while ClueBotNG is two:
// https://github.com/DamianZaremba/cluebotng/blob/4958e25d6874cba01c75f11debd2e511fd5a2ce5/bot/action_functions.php#L62
{
name: 'autolevelStaleDays',
type: 'integer'
},
// twinklebatchdelete.js: How many pages should be processed maximum
{
name: 'batchMax',
type: 'integer',
adminOnly: true
},
// twinklebatchdelete.js: How many pages should be processed at a time
{
name: 'batchdeleteChunks',
type: 'integer',
adminOnly: true
},
// twinklebatchprotect.js: How many pages should be processed at a time
{
name: 'batchProtectChunks',
type: 'integer',
adminOnly: true
},
// twinklebatchundelete.js: How many pages should be processed at a time
{
name: 'batchundeleteChunks',
type: 'integer',
adminOnly: true
},
// twinkledeprod.js: How many pages should be processed at a time
{
name: 'proddeleteChunks',
type: 'integer',
adminOnly: true
}
]
}
]; // end of Twinkle.config.sections
Twinkle.config.init = function twinkleconfigInit() {
// create the config page at Wikipedia:Twinkle/Preferences
if ((mw.config.get('wgNamespaceNumber') === mw.config.get('wgNamespaceIds').project && mw.config.get('wgTitle') === 'توینکل/ترجیحات') &&
mw.config.get('wgAction') === 'view') {
if (!document.getElementById('twinkle-config')) {
return; // maybe the page is misconfigured, or something - but any attempt to modify it will be pointless
}
// set style (the url() CSS function doesn't seem to work from wikicode - ?!)
document.getElementById('twinkle-config-titlebar').style.backgroundImage = 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB%2FqqA%2BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEhQTFRFr73ZobTPusjdsMHZp7nVwtDhzNbnwM3fu8jdq7vUt8nbxtDkw9DhpbfSvMrfssPZqLvVztbno7bRrr7W1d%2Fs1N7qydXk0NjpkW7Q%2BgAAADVJREFUeNoMwgESQCAAAMGLkEIi%2FP%2BnbnbpdB59app5Vdg0sXAoMZCpGoFbK6ciuy6FX4ABAEyoAef0BXOXAAAAAElFTkSuQmCC)';
var contentdiv = document.getElementById('twinkle-config-content');
contentdiv.textContent = ''; // clear children
// let user know about possible conflict with skin js/common.js file
// (settings in that file will still work, but they will be overwritten by twinkleoptions.js settings)
if (window.TwinkleConfig || window.FriendlyConfig) {
var contentnotice = document.createElement('p');
contentnotice.innerHTML = '<table class="plainlinks ombox ombox-content"><tr><td class="mbox-image">' +
'<img alt="" src="https://upload.wikimedia.org/wikipedia/commons/3/38/Imbox_content.png" /></td>' +
'<td class="mbox-text"><p><big><b>قبل از ویرایش تنظیمات در اینجا،</b> شما باید تنظیمات قبلی توینکل یا تنظیمات سفارشی تم خود را حذف کنید.</big></p>' + // localized
'<p>برای انجام آن شما میتوانید <a href="' + mw.util.getUrl('User:' + mw.config.get('wgUserName') + '/' + mw.config.get('skin') + // localized
'.js', { action: 'edit' }) + '" target="_blank"><b>فایل جاوااسکریپت شخصی خود</b></a> یا <a href="' + // localized
mw.util.getUrl('User:' + mw.config.get('wgUserName') + '/common.js', { action: 'edit'}) + '" target="_blank"><b>فایل common.js خود را ویرایش کرده و</b></a> همه خطهای کد که به <code>TwinkleConfig</code> و <code>FriendlyConfig</code> مرتبط است را حذف کنید.</p>' + // localized
'</td></tr></table>';
contentdiv.appendChild(contentnotice);
}
// start a table of contents
var toctable = document.createElement('div');
toctable.className = 'toc';
toctable.style.marginLeft = '0.4em';
// create TOC title
var toctitle = document.createElement('div');
toctitle.id = 'toctitle';
var toch2 = document.createElement('h2');
toch2.textContent = 'Contents ';
toctitle.appendChild(toch2);
// add TOC show/hide link
var toctoggle = document.createElement('span');
toctoggle.className = 'toctoggle';
toctoggle.appendChild(document.createTextNode('['));
var toctogglelink = document.createElement('a');
toctogglelink.className = 'internal';
toctogglelink.setAttribute('href', '#tw-tocshowhide');
toctogglelink.textContent = 'hide';
toctoggle.appendChild(toctogglelink);
toctoggle.appendChild(document.createTextNode(']'));
toctitle.appendChild(toctoggle);
toctable.appendChild(toctitle);
// create item container: this is what we add stuff to
var tocul = document.createElement('ul');
toctogglelink.addEventListener('click', function twinkleconfigTocToggle() {
var $tocul = $(tocul);
$tocul.toggle();
if ($tocul.find(':visible').length) {
toctogglelink.textContent = 'hide';
} else {
toctogglelink.textContent = 'show';
}
}, false);
toctable.appendChild(tocul);
contentdiv.appendChild(toctable);
var tocnumber = 1;
var contentform = document.createElement('form');
contentform.setAttribute('action', 'javascript:void(0)'); // was #tw-save - changed to void(0) to work around Chrome issue
contentform.addEventListener('submit', Twinkle.config.save, true);
contentdiv.appendChild(contentform);
var container = document.createElement('table');
container.style.width = '100%';
contentform.appendChild(container);
$(Twinkle.config.sections).each(function(sectionkey, section) {
if (section.hidden || (section.adminOnly && !Morebits.userIsSysop)) {
return true; // i.e. "continue" in this context
}
// add to TOC
var tocli = document.createElement('li');
tocli.className = 'toclevel-1';
var toca = document.createElement('a');
toca.setAttribute('href', '#twinkle-config-section-' + tocnumber.toString());
toca.appendChild(document.createTextNode(section.title));
tocli.appendChild(toca);
tocul.appendChild(tocli);
var row = document.createElement('tr');
var cell = document.createElement('td');
cell.setAttribute('colspan', '3');
var heading = document.createElement('h4');
heading.style.borderBottom = '1px solid gray';
heading.style.marginTop = '0.2em';
heading.id = 'twinkle-config-section-' + (tocnumber++).toString();
heading.appendChild(document.createTextNode(section.title));
cell.appendChild(heading);
row.appendChild(cell);
container.appendChild(row);
var rowcount = 1; // for row banding
// add each of the preferences to the form
$(section.preferences).each(function(prefkey, pref) {
if (pref.adminOnly && !Morebits.userIsSysop) {
return true; // i.e. "continue" in this context
}
row = document.createElement('tr');
row.style.marginBottom = '0.2em';
// create odd row banding
if (rowcount++ % 2 === 0) {
row.style.backgroundColor = 'rgba(128, 128, 128, 0.1)';
}
cell = document.createElement('td');
var label, input;
switch (pref.type) {
case 'boolean': // create a checkbox
cell.setAttribute('colspan', '2');
label = document.createElement('label');
input = document.createElement('input');
input.setAttribute('type', 'checkbox');
input.setAttribute('id', pref.name);
input.setAttribute('name', pref.name);
if (Twinkle.getPref(pref.name) === true) {
input.setAttribute('checked', 'checked');
}
label.appendChild(input);
label.appendChild(document.createTextNode(' ' + pref.label));
cell.appendChild(label);
break;
case 'string': // create an input box
case 'integer':
// add label to first column
cell.style.textAlign = 'right';
cell.style.paddingRight = '0.5em';
label = document.createElement('label');
label.setAttribute('for', pref.name);
label.appendChild(document.createTextNode(pref.label + ':'));
cell.appendChild(label);
row.appendChild(cell);
// add input box to second column
cell = document.createElement('td');
cell.style.paddingRight = '1em';
input = document.createElement('input');
input.setAttribute('type', 'text');
input.setAttribute('id', pref.name);
input.setAttribute('name', pref.name);
if (pref.type === 'integer') {
input.setAttribute('size', 6);
input.setAttribute('type', 'number');
input.setAttribute('step', '1'); // integers only
}
if (Twinkle.getPref(pref.name)) {
input.setAttribute('value', Twinkle.getPref(pref.name));
}
cell.appendChild(input);
break;
case 'enum': // create a combo box
// add label to first column
// note: duplicates the code above, under string/integer
cell.style.textAlign = 'right';
cell.style.paddingRight = '0.5em';
label = document.createElement('label');
label.setAttribute('for', pref.name);
label.appendChild(document.createTextNode(pref.label + ':'));
cell.appendChild(label);
row.appendChild(cell);
// add input box to second column
cell = document.createElement('td');
cell.style.paddingRight = '1em';
input = document.createElement('select');
input.setAttribute('id', pref.name);
input.setAttribute('name', pref.name);
$.each(pref.enumValues, function(enumvalue, enumdisplay) {
var option = document.createElement('option');
option.setAttribute('value', enumvalue);
if (Twinkle.getPref(pref.name) === enumvalue) {
option.setAttribute('selected', 'selected');
}
option.appendChild(document.createTextNode(enumdisplay));
input.appendChild(option);
});
cell.appendChild(input);
break;
case 'set': // create a set of check boxes
// add label first of all
cell.setAttribute('colspan', '2');
label = document.createElement('label'); // not really necessary to use a label element here, but we do it for consistency of styling
label.appendChild(document.createTextNode(pref.label + ':'));
cell.appendChild(label);
var checkdiv = document.createElement('div');
checkdiv.style.paddingLeft = '1em';
var worker = function(itemkey, itemvalue) {
var checklabel = document.createElement('label');
checklabel.style.marginRight = '0.7em';
checklabel.style.display = 'inline-block';
var check = document.createElement('input');
check.setAttribute('type', 'checkbox');
check.setAttribute('id', pref.name + '_' + itemkey);
check.setAttribute('name', pref.name + '_' + itemkey);
if (Twinkle.getPref(pref.name) && Twinkle.getPref(pref.name).indexOf(itemkey) !== -1) {
check.setAttribute('checked', 'checked');
}
// cater for legacy integer array values for unlinkNamespaces (this can be removed a few years down the track...)
if (pref.name === 'unlinkNamespaces') {
if (Twinkle.getPref(pref.name) && Twinkle.getPref(pref.name).indexOf(parseInt(itemkey, 10)) !== -1) {
check.setAttribute('checked', 'checked');
}
}
checklabel.appendChild(check);
checklabel.appendChild(document.createTextNode(itemvalue));
checkdiv.appendChild(checklabel);
};
if (pref.setDisplayOrder) {
// add check boxes according to the given display order
$.each(pref.setDisplayOrder, function(itemkey, item) {
worker(item, pref.setValues[item]);
});
} else {
// add check boxes according to the order it gets fed to us (probably strict alphabetical)
$.each(pref.setValues, worker);
}
cell.appendChild(checkdiv);
break;
case 'customList':
// add label to first column
cell.style.textAlign = 'right';
cell.style.paddingRight = '0.5em';
label = document.createElement('label');
label.setAttribute('for', pref.name);
label.appendChild(document.createTextNode(pref.label + ':'));
cell.appendChild(label);
row.appendChild(cell);
// add button to second column
cell = document.createElement('td');
cell.style.paddingRight = '1em';
var button = document.createElement('button');
button.setAttribute('id', pref.name);
button.setAttribute('name', pref.name);
button.setAttribute('type', 'button');
button.addEventListener('click', Twinkle.config.listDialog.display, false);
// use jQuery data on the button to store the current config value
$(button).data({
value: Twinkle.getPref(pref.name),
pref: pref
});
button.appendChild(document.createTextNode('ویرایش آیتمها')); // localized
cell.appendChild(button);
break;
default:
alert('twinkleconfig: unknown data type for preference ' + pref.name);
break;
}
row.appendChild(cell);
// add help tip
cell = document.createElement('td');
cell.style.fontSize = '90%';
cell.style.color = 'gray';
if (pref.helptip) {
// convert mentions of templates in the helptip to clickable links
cell.innerHTML = pref.helptip.replace(/{{(.+?)}}/g,
'{{<a href="' + mw.util.getUrl('Template:') + '$1" target="_blank">$1</a>}}');
}
// add reset link (custom lists don't need this, as their config value isn't displayed on the form)
if (pref.type !== 'customList') {
var resetlink = document.createElement('a');
resetlink.setAttribute('href', '#tw-reset');
resetlink.setAttribute('id', 'twinkle-config-reset-' + pref.name);
resetlink.addEventListener('click', Twinkle.config.resetPrefLink, false);
resetlink.style.cssFloat = 'right';
resetlink.style.margin = '0 0.6em';
resetlink.appendChild(document.createTextNode('بازگشت به مقدار پیشفرض')); // localized
cell.appendChild(resetlink);
}
row.appendChild(cell);
container.appendChild(row);
return true;
});
return true;
});
var footerbox = document.createElement('div');
footerbox.setAttribute('id', 'twinkle-config-buttonpane');
footerbox.style.backgroundColor = '#BCCADF';
footerbox.style.padding = '0.5em';
var button = document.createElement('button');
button.setAttribute('id', 'twinkle-config-submit');
button.setAttribute('type', 'submit');
button.appendChild(document.createTextNode('ذخیره کردن تغییرات')); // localized
footerbox.appendChild(button);
var footerspan = document.createElement('span');
footerspan.className = 'plainlinks';
footerspan.style.marginLeft = '2.4em';
footerspan.style.fontSize = '90%';
var footera = document.createElement('a');
footera.setAttribute('href', '#tw-reset-all');
footera.setAttribute('id', 'twinkle-config-resetall');
footera.addEventListener('click', Twinkle.config.resetAllPrefs, false);
footera.appendChild(document.createTextNode('بازگرداندن مقادیر پیشفرض')); // localized
footerspan.appendChild(footera);
footerbox.appendChild(footerspan);
contentform.appendChild(footerbox);
// since all the section headers exist now, we can try going to the requested anchor
if (location.hash) {
window.location.hash = location.hash;
}
} else if (mw.config.get('wgNamespaceNumber') === mw.config.get('wgNamespaceIds').user &&
mw.config.get('wgTitle').indexOf(mw.config.get('wgUserName')) === 0 &&
mw.config.get('wgPageName').slice(-3) === '.js') {
var box = document.createElement('div');
// Styled in twinkle.css
box.setAttribute('id', 'twinkle-config-headerbox');
var link,
scriptPageName = mw.config.get('wgPageName').slice(mw.config.get('wgPageName').lastIndexOf('/') + 1,
mw.config.get('wgPageName').lastIndexOf('.js'));
if (scriptPageName === 'twinkleoptions') {
// place "why not try the preference panel" notice
box.setAttribute('class', 'config-twopt-box');
if (mw.config.get('wgArticleId') > 0) { // page exists
box.appendChild(document.createTextNode('این صفحه شامل تنظیمات توینکل است. شما میتوانید آن را در ')); // localized
} else { // page does not exist
box.appendChild(document.createTextNode('شما میتوانید تنظیمات سفارشی خود برای توینکل را در ')); // localized
}
link = document.createElement('a');
link.setAttribute('href', mw.util.getUrl(mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').project] + ':توینکل/ترجیحات'));
link.appendChild(document.createTextNode('پنل تنظیمات توینکل')); // localized
box.appendChild(link);
box.appendChild(document.createTextNode('تغییر دهید یا این صفحه را ویرایش کنید.')); // localized
$(box).insertAfter($('#contentSub'));
} else if (['monobook', 'vector', 'cologneblue', 'modern', 'timeless', 'minerva', 'common'].indexOf(scriptPageName) !== -1) {
// place "Looking for Twinkle options?" notice
box.setAttribute('class', 'config-userskin-box');
box.appendChild(document.createTextNode('اگر میخواهید تنظیمات توینکل را تغییر دهید میتوانید از ')); // localized
link = document.createElement('a');
link.setAttribute('href', mw.util.getUrl(mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').project] + ':توینکل/ترجیحات'));
link.appendChild(document.createTextNode('پنل تنظیمات توینکل')); // localized
box.appendChild(link);
box.appendChild(document.createTextNode('استفاده کنید.')); // localized
$(box).insertAfter($('#contentSub'));
}
}
};
// custom list-related stuff
Twinkle.config.listDialog = {};
Twinkle.config.listDialog.addRow = function twinkleconfigListDialogAddRow(dlgtable, value, label) {
var contenttr = document.createElement('tr');
// "remove" button
var contenttd = document.createElement('td');
var removeButton = document.createElement('button');
removeButton.setAttribute('type', 'button');
removeButton.addEventListener('click', function() {
$(contenttr).remove();
}, false);
removeButton.textContent = 'حذف'; // localized
contenttd.appendChild(removeButton);
contenttr.appendChild(contenttd);
// value input box
contenttd = document.createElement('td');
var input = document.createElement('input');
input.setAttribute('type', 'text');
input.className = 'twinkle-config-customlist-value';
input.style.width = '97%';
if (value) {
input.setAttribute('value', value);
}
contenttd.appendChild(input);
contenttr.appendChild(contenttd);
// label input box
contenttd = document.createElement('td');
input = document.createElement('input');
input.setAttribute('type', 'text');
input.className = 'twinkle-config-customlist-label';
input.style.width = '98%';
if (label) {
input.setAttribute('value', label);
}
contenttd.appendChild(input);
contenttr.appendChild(contenttd);
dlgtable.appendChild(contenttr);
};
Twinkle.config.listDialog.display = function twinkleconfigListDialogDisplay(e) {
var $prefbutton = $(e.target);
var curvalue = $prefbutton.data('value');
var curpref = $prefbutton.data('pref');
var dialog = new Morebits.simpleWindow(720, 400);
dialog.setTitle(curpref.label);
dialog.setScriptName('تنظیمات توینکل'); // localized
var dialogcontent = document.createElement('div');
var dlgtable = document.createElement('table');
dlgtable.className = 'wikitable';
dlgtable.style.margin = '1.4em 1em';
dlgtable.style.width = 'auto';
var dlgtbody = document.createElement('tbody');
// header row
var dlgtr = document.createElement('tr');
// top-left cell
var dlgth = document.createElement('th');
dlgth.style.width = '5%';
dlgtr.appendChild(dlgth);
// value column header
dlgth = document.createElement('th');
dlgth.style.width = '35%';
dlgth.textContent = curpref.customListValueTitle ? curpref.customListValueTitle : 'Value';
dlgtr.appendChild(dlgth);
// label column header
dlgth = document.createElement('th');
dlgth.style.width = '60%';
dlgth.textContent = curpref.customListLabelTitle ? curpref.customListLabelTitle : 'Label';
dlgtr.appendChild(dlgth);
dlgtbody.appendChild(dlgtr);
// content rows
var gotRow = false;
$.each(curvalue, function(k, v) {
gotRow = true;
Twinkle.config.listDialog.addRow(dlgtbody, v.value, v.label);
});
// if there are no values present, add a blank row to start the user off
if (!gotRow) {
Twinkle.config.listDialog.addRow(dlgtbody);
}
// final "add" button
var dlgtfoot = document.createElement('tfoot');
dlgtr = document.createElement('tr');
var dlgtd = document.createElement('td');
dlgtd.setAttribute('colspan', '3');
var addButton = document.createElement('button');
addButton.style.minWidth = '8em';
addButton.setAttribute('type', 'button');
addButton.addEventListener('click', function() {
Twinkle.config.listDialog.addRow(dlgtbody);
}, false);
addButton.textContent = 'اضافهکردن'; // localized
dlgtd.appendChild(addButton);
dlgtr.appendChild(dlgtd);
dlgtfoot.appendChild(dlgtr);
dlgtable.appendChild(dlgtbody);
dlgtable.appendChild(dlgtfoot);
dialogcontent.appendChild(dlgtable);
// buttonpane buttons: [Save changes] [Reset] [Cancel]
var button = document.createElement('button');
button.setAttribute('type', 'submit'); // so Morebits.simpleWindow puts the button in the button pane
button.addEventListener('click', function() {
Twinkle.config.listDialog.save($prefbutton, dlgtbody);
dialog.close();
}, false);
button.textContent = 'ذخیره تغییرات'; // localized
dialogcontent.appendChild(button);
button = document.createElement('button');
button.setAttribute('type', 'submit'); // so Morebits.simpleWindow puts the button in the button pane
button.addEventListener('click', function() {
Twinkle.config.listDialog.reset($prefbutton, dlgtbody);
}, false);
button.textContent = 'بازنشانی'; // localized
dialogcontent.appendChild(button);
button = document.createElement('button');
button.setAttribute('type', 'submit'); // so Morebits.simpleWindow puts the button in the button pane
button.addEventListener('click', function() {
dialog.close(); // the event parameter on this function seems to be broken
}, false);
button.textContent = 'لغو'; // localized
dialogcontent.appendChild(button);
dialog.setContent(dialogcontent);
dialog.display();
};
// Resets the data value, re-populates based on the new (default) value, then saves the
// old data value again (less surprising behaviour)
Twinkle.config.listDialog.reset = function twinkleconfigListDialogReset(button, tbody) {
// reset value on button
var $button = $(button);
var curpref = $button.data('pref');
var oldvalue = $button.data('value');
Twinkle.config.resetPref(curpref);
// reset form
var $tbody = $(tbody);
$tbody.find('tr').slice(1).remove(); // all rows except the first (header) row
// add the new values
var curvalue = $button.data('value');
$.each(curvalue, function(k, v) {
Twinkle.config.listDialog.addRow(tbody, v.value, v.label);
});
// save the old value
$button.data('value', oldvalue);
};
Twinkle.config.listDialog.save = function twinkleconfigListDialogSave(button, tbody) {
var result = [];
var current = {};
$(tbody).find('input[type="text"]').each(function(inputkey, input) {
if ($(input).hasClass('twinkle-config-customlist-value')) {
current = { value: input.value };
} else {
current.label = input.value;
// exclude totally empty rows
if (current.value || current.label) {
result.push(current);
}
}
});
$(button).data('value', result);
};
// reset/restore defaults
Twinkle.config.resetPrefLink = function twinkleconfigResetPrefLink(e) {
var wantedpref = e.target.id.substring(21); // "twinkle-config-reset-" prefix is stripped
// search tactics
$(Twinkle.config.sections).each(function(sectionkey, section) {
if (section.hidden || (section.adminOnly && !Morebits.userIsSysop)) {
return true; // continue: skip impossibilities
}
var foundit = false;
$(section.preferences).each(function(prefkey, pref) {
if (pref.name !== wantedpref) {
return true; // continue
}
Twinkle.config.resetPref(pref);
foundit = true;
return false; // break
});
if (foundit) {
return false; // break
}
});
return false; // stop link from scrolling page
};
Twinkle.config.resetPref = function twinkleconfigResetPref(pref) {
switch (pref.type) {
case 'boolean':
document.getElementById(pref.name).checked = Twinkle.defaultConfig[pref.name];
break;
case 'string':
case 'integer':
case 'enum':
document.getElementById(pref.name).value = Twinkle.defaultConfig[pref.name];
break;
case 'set':
$.each(pref.setValues, function(itemkey) {
if (document.getElementById(pref.name + '_' + itemkey)) {
document.getElementById(pref.name + '_' + itemkey).checked = Twinkle.defaultConfig[pref.name].indexOf(itemkey) !== -1;
}
});
break;
case 'customList':
$(document.getElementById(pref.name)).data('value', Twinkle.defaultConfig[pref.name]);
break;
default:
alert('تنظیمات توینکل: نوع داده برای ' + pref.name + ' اشتباه است'); // localized
break;
}
};
Twinkle.config.resetAllPrefs = function twinkleconfigResetAllPrefs() {
// no confirmation message - the user can just refresh/close the page to abort
$(Twinkle.config.sections).each(function(sectionkey, section) {
if (section.hidden || (section.adminOnly && !Morebits.userIsSysop)) {
return true; // continue: skip impossibilities
}
$(section.preferences).each(function(prefkey, pref) {
if (!pref.adminOnly || Morebits.userIsSysop) {
Twinkle.config.resetPref(pref);
}
});
return true;
});
return false; // stop link from scrolling page
};
Twinkle.config.save = function twinkleconfigSave(e) {
Morebits.status.init(document.getElementById('twinkle-config-content'));
var userjs = mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').user] + ':' + mw.config.get('wgUserName') + '/twinkleoptions.js';
var wikipedia_page = new Morebits.wiki.page(userjs, 'ذخیره تنظیمات در ' + userjs); // localized
wikipedia_page.setCallbackParameters(e.target);
wikipedia_page.load(Twinkle.config.writePrefs);
return false;
};
Twinkle.config.writePrefs = function twinkleconfigWritePrefs(pageobj) {
var form = pageobj.getCallbackParameters();
// this is the object which gets serialized into JSON; only
// preferences that this script knows about are kept
var newConfig = {optionsVersion: 2};
// a comparison function is needed later on
// it is just enough for our purposes (i.e. comparing strings, numbers, booleans,
// arrays of strings, and arrays of { value, label })
// and it is not very robust: e.g. compare([2], ["2"]) === true, and
// compare({}, {}) === false, but it's good enough for our purposes here
var compare = function(a, b) {
if (Array.isArray(a)) {
if (a.length !== b.length) {
return false;
}
var asort = a.sort(), bsort = b.sort();
for (var i = 0; asort[i]; ++i) {
// comparison of the two properties of custom lists
if ((typeof asort[i] === 'object') && (asort[i].label !== bsort[i].label ||
asort[i].value !== bsort[i].value)) {
return false;
} else if (asort[i].toString() !== bsort[i].toString()) {
return false;
}
}
return true;
}
return a === b;
};
$(Twinkle.config.sections).each(function(sectionkey, section) {
if (section.adminOnly && !Morebits.userIsSysop) {
return; // i.e. "continue" in this context
}
// reach each of the preferences from the form
$(section.preferences).each(function(prefkey, pref) {
var userValue; // = undefined
// only read form values for those prefs that have them
if (!pref.adminOnly || Morebits.userIsSysop) {
if (!section.hidden) {
switch (pref.type) {
case 'boolean': // read from the checkbox
userValue = form[pref.name].checked;
break;
case 'string': // read from the input box or combo box
case 'enum':
userValue = form[pref.name].value;
break;
case 'integer': // read from the input box
userValue = parseInt(form[pref.name].value, 10);
if (isNaN(userValue)) {
Morebits.status.warn('در حال ذخیره', 'مقداری که برای ' + pref.name + ' (' + pref.value + ') انتخاب کردهاید اشتباه است. ذخیره کردن ادامه مییابد اما مقدار مشکلدار ثبت نمیشود.'); // localized
userValue = null;
}
break;
case 'set': // read from the set of check boxes
userValue = [];
if (pref.setDisplayOrder) {
// read only those keys specified in the display order
$.each(pref.setDisplayOrder, function(itemkey, item) {
if (form[pref.name + '_' + item].checked) {
userValue.push(item);
}
});
} else {
// read all the keys in the list of values
$.each(pref.setValues, function(itemkey) {
if (form[pref.name + '_' + itemkey].checked) {
userValue.push(itemkey);
}
});
}
break;
case 'customList': // read from the jQuery data stored on the button object
userValue = $(form[pref.name]).data('value');
break;
default:
alert('twinkleconfig: unknown data type for preference ' + pref.name);
break;
}
} else if (Twinkle.prefs) {
// Retain the hidden preferences that may have customised by the user from twinkleoptions.js
// undefined if not set
userValue = Twinkle.prefs[pref.name];
}
}
// only save those preferences that are *different* from the default
if (userValue !== undefined && !compare(userValue, Twinkle.defaultConfig[pref.name])) {
newConfig[pref.name] = userValue;
}
});
});
var text =
'// twinkleoptions.js: personal Twinkle preferences file\n' +
'//\n' +
'// NOTE: The easiest way to change your Twinkle preferences is by using the\n' +
'// Twinkle preferences panel, at [[' + Morebits.pageNameNorm + ']].\n' +
'//\n' +
'// This file is AUTOMATICALLY GENERATED. Any changes you make (aside from\n' +
'// changing the configuration parameters in a valid-JavaScript way) will be\n' +
'// overwritten the next time you click "save" in the Twinkle preferences\n' +
'// panel. If modifying this file, make sure to use correct JavaScript.\n' +
'// <no' + 'wiki>\n' +
'\n' +
'window.Twinkle.prefs = ';
text += JSON.stringify(newConfig, null, 2);
text +=
';\n' +
'\n' +
'// </no' + 'wiki>\n' +
'// End of twinkleoptions.js\n';
pageobj.setPageText(text);
pageobj.setEditSummary('ذخیره تنظیمات توینکل: ویرایش خودکار از [[:' + Morebits.pageNameNorm + ']] ([[وو:تل]])'); // localized
pageobj.setCreateOption('recreate');
pageobj.save(Twinkle.config.saveSuccess);
};
Twinkle.config.saveSuccess = function twinkleconfigSaveSuccess(pageobj) {
pageobj.getStatusElement().info('successful');
var noticebox = document.createElement('div');
noticebox.className = 'successbox';
noticebox.style.fontSize = '100%';
noticebox.style.marginTop = '2em';
noticebox.innerHTML = '<p><b>تنظیمات توینکل شما ذخیره شد.</b></p><p>برای دیدن تغییرات باید <b>کاشه مرورگر را خالی کنید</b> (توضیح بیشتر در<a href="' + mw.util.getUrl('WP:BYPASS') + '" title="وو:کاشه">وو:کاشه</a>).</p>'; // localized
Morebits.status.root.appendChild(noticebox);
var noticeclear = document.createElement('br');
noticeclear.style.clear = 'both';
Morebits.status.root.appendChild(noticeclear);
};
})(jQuery);
// </nowiki>
3ogwggawr86yn3gcjxpcdeokl180yxs
کاربر:Mamouri/twinkle/twinklewarn-2020.js
2
219485
908992
2026-07-30T16:04:02Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
908992
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** twinklewarn.js: Warn module
****************************************
* Mode of invocation: Tab ("Warn")
* Active on: Any page with relevant user name (userspace, contribs,
* etc.), as well as the rollback success page
*/
Twinkle.warn = function twinklewarn() {
if (mw.config.get('wgRelevantUserName')) {
Twinkle.addPortletLink(Twinkle.warn.callback, 'هشدار', 'tw-warn', 'هشدار/اعلان به کاربر'); // localized
if (Twinkle.getPref('autoMenuAfterRollback') &&
mw.config.get('wgNamespaceNumber') === 3 &&
mw.util.getParamValue('vanarticle') &&
!mw.util.getParamValue('friendlywelcome') &&
!mw.util.getParamValue('noautowarn')) {
Twinkle.warn.callback();
}
}
// Modify URL of talk page on rollback success pages, makes use of a
// custom message box in [[MediaWiki:Rollback-success]]
if (mw.config.get('wgAction') === 'rollback') {
var $vandalTalkLink = $('#mw-rollback-success').find('.mw-usertoollinks a').first();
if ($vandalTalkLink.length) {
$vandalTalkLink.css('font-weight', 'bold');
$vandalTalkLink.wrapInner($('<span/>').attr('title', 'در صورت صلاحدید، میتوانید از توینکل برای هشدار به کاربر دربارهٔ ویرایشهایش در این صفحه استفاده کنید.')); // localized
// Can't provide vanarticlerevid as only wgCurRevisionId is provided
var extraParam = 'vanarticle=' + mw.util.rawurlencode(Morebits.pageNameNorm);
var href = $vandalTalkLink.attr('href');
if (href.indexOf('?') === -1) {
$vandalTalkLink.attr('href', href + '?' + extraParam);
} else {
$vandalTalkLink.attr('href', href + '&' + extraParam);
}
}
}
};
// Used to close window when switching to ARV in autolevel
Twinkle.warn.dialog = null;
Twinkle.warn.callback = function twinklewarnCallback() {
if (mw.config.get('wgRelevantUserName') === mw.config.get('wgUserName') &&
!confirm('شما در حال هشدار به خودتان هستید! آیا از انجام این کار مطمئن هستید؟')) { // localized
return;
}
var dialog;
Twinkle.warn.dialog = new Morebits.simpleWindow(600, 440);
dialog = Twinkle.warn.dialog;
dialog.setTitle('هشدار/اعلان به کاربر'); // localized
dialog.setScriptName('توینکل'); // localized
dialog.addFooterLink('انتخاب سطح هشدار', 'en:WP:UWUL#Levels'); // localized
dialog.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#warn'); // localized
var form = new Morebits.quickForm(Twinkle.warn.callback.evaluate);
var main_select = form.append({
type: 'field',
label: 'نوع هشدار/اعلان برای این مشکل را انتخاب کنید', // localized
tooltip: 'ابتدا گروه اصلی هشدار، و سپس یک هشدار اختصاصی برای این مشکل را انتحاب کنید.' // localized
});
var main_group = main_select.append({
type: 'select',
name: 'main_group',
event: Twinkle.warn.callback.change_category
});
var defaultGroup = parseInt(Twinkle.getPref('defaultWarningGroup'), 10);
main_group.append({ type: 'option', label: 'هشدار خرابکاری (نخستین هشدار)', value: 'singlenotice', selected: defaultGroup !== 7 }); // localized
main_group.append({ type: 'option', label: 'آخرین هشدار خرابکاری', value: 'singlewarn', selected: defaultGroup === 7 }); // localized
main_select.append({ type: 'select', name: 'sub_group', event: Twinkle.warn.callback.change_subcategory }); // Will be empty to begin with.
form.append({
type: 'input',
name: 'article',
label: 'صفحهٔ پیوند دادهشده', // localized
value: mw.util.getParamValue('vanarticle') || '',
tooltip: 'میتوان یک صفحه را از درون اعلان پیوند داد، شاید به این دلیل که این اعلان بهواسطهٔ یک خنثیسازی در صفحهٔ مذکور ارسال شدهاست. برای اینکه پیوندی داده نشود، این جعبه را خالی بگذارید.' // localized
});
form.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-warn-revert-messages'
});
var vanrevid = mw.util.getParamValue('vanarticlerevid');
if (vanrevid) {
var message = '';
var query = {};
// If you tried reverting, check if *you* actually reverted
if (!mw.util.getParamValue('noautowarn') && mw.util.getParamValue('vanarticle')) { // Via fluff link
query = {
action: 'query',
titles: mw.util.getParamValue('vanarticle'),
prop: 'revisions',
rvstartid: vanrevid,
rvlimit: 2,
rvdir: 'newer',
rvprop: 'user'
};
new Morebits.wiki.api('بررسی این که واگردانی شما در این صفحه موفق بود یا نه', query, function(apiobj) { // localized
var revertUser = $(apiobj.getResponse()).find('revisions rev')[1].getAttribute('user');
if (revertUser && revertUser !== mw.config.get('wgUserName')) {
message += ' کس دیگری این صفحه را واگردانی کردهاست و ممکن است به کاربر نیز هشدار دادهباشد.'; // localized
$('#twinkle-warn-revert-messages').text('Note:' + message);
}
}).post();
}
// Confirm edit wasn't too old for a warning
query = {
action: 'query',
prop: 'revisions',
rvprop: 'timestamp',
revids: vanrevid
};
new Morebits.wiki.api('گرفتن برچسب زمان نسخهها', query, function(apiobj) { // localized
var vantimestamp = $(apiobj.getResponse()).find('revisions rev').attr('timestamp');
var revDate = new Morebits.date(vantimestamp);
if (vantimestamp && revDate.isValid()) {
if (revDate.add(24, 'hours').isBefore(new Date())) {
message += ' این ویرایش بیشتر از ۲۴ ساعت پیش انجام شدهاست و ممکن است نیازی به هشدار نباشد.'; // localized
$('#twinkle-warn-revert-messages').text('Note:' + message);
}
}
}).post();
}
var more = form.append({ type: 'field', name: 'reasonGroup', label: 'اطلاعات هشدار' }); // localized
more.append({ type: 'textarea', label: 'پیام اختیاری:', name: 'reason', tooltip: 'شاید لازم باشد یک دلیل یا یک اعلان با جزئیات بیشتر اضافه شود' }); // localized
var previewlink = document.createElement('a');
$(previewlink).click(function() {
Twinkle.warn.callbacks.preview(result); // |result| is defined below
});
previewlink.style.cursor = 'pointer';
previewlink.textContent = 'پیشنمایش'; // localized
more.append({ type: 'div', id: 'warningpreview', label: [ previewlink ] });
more.append({ type: 'div', id: 'twinklewarn-previewbox', style: 'display: none' });
more.append({ type: 'submit', label: 'ثبت' }); // localized
var result = form.render();
dialog.setContent(result);
dialog.display();
result.main_group.root = result;
result.previewer = new Morebits.wiki.preview($(result).find('div#twinklewarn-previewbox').last()[0]);
// We must init the first choice (General Note);
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.main_group.dispatchEvent(evt);
};
// This is all the messages that might be dispatched by the code
// Each of the individual templates require the following information:
// label (required): A short description displayed in the dialog
// summary (required): The edit summary used. If an article name is entered, the summary is postfixed with "on [[article]]", and it is always postfixed with ". $summaryAd"
// suppressArticleInSummary (optional): Set to true to suppress showing the article name in the edit summary. Useful if the warning relates to attack pages, or some such.
Twinkle.warn.messages = {
levels: {},
singlenotice: {
'هشدار خرابکاری': {
label: 'هشدار خرابکاری — نخستین هشدار به خرابکار',
summary: 'هشدار خرابکاری'
}
},
singlewarn: {
'آخرین هشدار خرابکاری': {
label: 'آخرین هشدار خرابکاری',
summary: 'آخرین هشدار خرابکاری'
}
}
};
Twinkle.warn.talkpageObj = null;
Twinkle.warn.callback.change_category = function twinklewarnCallbackChangeCategory(e) {
var value = e.target.value;
var sub_group = e.target.root.sub_group;
sub_group.main_group = value;
var old_subvalue = sub_group.value;
var old_subvalue_re;
if (old_subvalue) {
if (value === 'kitchensink') { // Exact match possible in kitchensink menu
old_subvalue_re = new RegExp(mw.util.escapeRegExp(old_subvalue));
} else {
old_subvalue = old_subvalue.replace(/\d*(im)?$/, '');
old_subvalue_re = new RegExp(mw.util.escapeRegExp(old_subvalue) + '(\\d*(?:im)?)$');
}
}
while (sub_group.hasChildNodes()) {
sub_group.removeChild(sub_group.firstChild);
}
var selected = false;
// worker function to create the combo box entries
var createEntries = function(contents, container, wrapInOptgroup, val) {
val = typeof val !== 'undefined' ? val : value; // IE doesn't support default parameters
// level2->2, singlewarn->''; also used to distinguish the
// scaled levels from singlenotice, singlewarn, and custom
var level = val.replace(/^\D+/g, '');
// due to an apparent iOS bug, we have to add an option-group to prevent truncation of text
// (search WT:TW archives for "Problem selecting warnings on an iPhone")
if (wrapInOptgroup && $.client.profile().platform === 'iphone') {
var wrapperOptgroup = new Morebits.quickForm.element({
type: 'optgroup',
label: 'الگوهای موجود' // localized
});
wrapperOptgroup = wrapperOptgroup.render();
container.appendChild(wrapperOptgroup);
container = wrapperOptgroup;
}
$.each(contents, function(itemKey, itemProperties) {
// Skip if the current template doesn't have a version for the current level
if (!!level && !itemProperties[val]) {
return;
}
var key = typeof itemKey === 'string' ? itemKey : itemProperties.value;
var template = key + level;
var elem = new Morebits.quickForm.element({
type: 'option',
label: '{{' + template + '}}: ' + (level ? itemProperties[val].label : itemProperties.label),
value: template
});
// Select item best corresponding to previous selection
if (!selected && old_subvalue && old_subvalue_re.test(template)) {
elem.data.selected = selected = true;
}
var elemRendered = container.appendChild(elem.render());
$(elemRendered).data('messageData', itemProperties);
});
};
switch (value) {
case 'singlenotice':
case 'singlewarn':
createEntries(Twinkle.warn.messages[value], sub_group, true);
break;
case 'singlecombined':
var unSortedSinglets = $.extend({}, Twinkle.warn.messages.singlenotice, Twinkle.warn.messages.singlewarn);
var sortedSingletMessages = {};
Object.keys(unSortedSinglets).sort().forEach(function(key) {
sortedSingletMessages[key] = unSortedSinglets[key];
});
createEntries(sortedSingletMessages, sub_group, true);
break;
case 'custom':
createEntries(Twinkle.getPref('customWarningList'), sub_group, true);
break;
case 'kitchensink':
['level1', 'level2', 'level3', 'level4', 'level4im'].forEach(function(lvl) {
$.each(Twinkle.warn.messages.levels, function(_, levelGroup) {
createEntries(levelGroup, sub_group, true, lvl);
});
});
createEntries(Twinkle.warn.messages.singlenotice, sub_group, true);
createEntries(Twinkle.warn.messages.singlewarn, sub_group, true);
createEntries(Twinkle.getPref('customWarningList'), sub_group, true);
break;
case 'level1':
case 'level2':
case 'level3':
case 'level4':
case 'level4im':
// Creates subgroup regardless of whether there is anything to place in it;
// leaves "Removal of deletion tags" empty for 4im
$.each(Twinkle.warn.messages.levels, function(groupLabel, groupContents) {
var optgroup = new Morebits.quickForm.element({
type: 'optgroup',
label: groupLabel
});
optgroup = optgroup.render();
sub_group.appendChild(optgroup);
// create the options
createEntries(groupContents, optgroup, false);
});
break;
case 'autolevel':
// Check user page to determine appropriate level
var autolevelProc = function() {
var wikitext = Twinkle.warn.talkpageObj.getPageText();
// history not needed for autolevel
var latest = Twinkle.warn.callbacks.dateProcessing(wikitext)[0];
// Pseudo-params with only what's needed to parse the level i.e. no messageData
var params = {
sub_group: old_subvalue,
article: e.target.root.article.value
};
var lvl = 'level' + Twinkle.warn.callbacks.autolevelParseWikitext(wikitext, params, latest)[1];
// Identical to level1, etc. above but explicitly provides the level
$.each(Twinkle.warn.messages.levels, function(groupLabel, groupContents) {
var optgroup = new Morebits.quickForm.element({
type: 'optgroup',
label: groupLabel
});
optgroup = optgroup.render();
sub_group.appendChild(optgroup);
// create the options
createEntries(groupContents, optgroup, false, lvl);
});
// Trigger subcategory change, add select menu, etc.
Twinkle.warn.callback.postCategoryCleanup(e);
};
if (Twinkle.warn.talkpageObj) {
autolevelProc();
} else {
var usertalk_page = new Morebits.wiki.page('بحث کاربر:' + mw.config.get('wgRelevantUserName'), 'بارگیری هشدارهای قبلی'); // localized
usertalk_page.setFollowRedirect(true);
usertalk_page.load(function(pageobj) {
Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj
autolevelProc();
});
}
break;
default:
alert('Unknown warning group in twinklewarn');
break;
}
// Trigger subcategory change, add select menu, etc.
// Here because of the async load for autolevel
if (value !== 'autolevel') {
// reset any autolevel-specific messages while we're here
$('#twinkle-warn-autolevel-message').remove();
Twinkle.warn.callback.postCategoryCleanup(e);
}
};
Twinkle.warn.callback.postCategoryCleanup = function twinklewarnCallbackPostCategoryCleanup(e) {
// clear overridden label on article textbox
Morebits.quickForm.setElementTooltipVisibility(e.target.root.article, true);
Morebits.quickForm.resetElementLabel(e.target.root.article);
// Trigger custom label/change on main category change
Twinkle.warn.callback.change_subcategory(e);
// Use select2 to make the select menu searchable
if (!Twinkle.getPref('oldSelect')) {
$('select[name=sub_group]')
.select2({
width: '100%',
matcher: Morebits.select2.matchers.optgroupFull,
templateResult: Morebits.select2.highlightSearchMatches,
language: {
searching: Morebits.select2.queryInterceptor
}
})
.change(Twinkle.warn.callback.change_subcategory);
$('.select2-selection').keydown(Morebits.select2.autoStart);
mw.util.addCSS(
// prevent dropdown from appearing behind the dialog, just in case
'.select2-container { z-index: 10000; }' +
// Increase height
'.select2-container .select2-dropdown .select2-results > .select2-results__options { max-height: 350px; }' +
// Reduce padding
'.select2-results .select2-results__option { padding-top: 1px; padding-bottom: 1px; }' +
'.select2-results .select2-results__group { padding-top: 1px; padding-bottom: 1px; } ' +
// Adjust font size
'.select2-container .select2-dropdown .select2-results { font-size: 13px; }' +
'.select2-container .selection .select2-selection__rendered { font-size: 13px; }'
);
}
};
Twinkle.warn.callback.change_subcategory = function twinklewarnCallbackChangeSubcategory(e) {
var main_group = e.target.form.main_group.value;
var value = e.target.form.sub_group.value;
// Tags that don't take a linked article, but something else (often a username).
// The value of each tag is the label next to the input field
var notLinkedArticle = {
'uw-agf-sock': 'اختیاری: نام کاربری حساب دیگر (بدون پیشوند کاربر:) ', // localized
'uw-bite': "نام کاربری کاربر «چماقخورده» (بدون پیشوند کاربر:) ", // localized
'uw-socksuspect': 'نام کاربری حساب اصلی، در صورت اطلاع (بدون پیشوند کاربر:) ', // localized
'uw-username': 'نام کاربری ناقض سیاست است زیرا... ', // localized
'uw-aiv': 'اختیاری: نام کاربری گزارششده (بدون پیشوند کاربر:) ' // localized
};
if (['singlenotice', 'singlewarn', 'singlecombined', 'kitchensink'].indexOf(main_group) !== -1) {
if (notLinkedArticle[value]) {
if (Twinkle.warn.prev_article === null) {
Twinkle.warn.prev_article = e.target.form.article.value;
}
e.target.form.article.notArticle = true;
e.target.form.article.value = '';
// change form labels according to the warning selected
Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, false);
Morebits.quickForm.overrideElementLabel(e.target.form.article, notLinkedArticle[value]);
} else if (e.target.form.article.notArticle) {
if (Twinkle.warn.prev_article !== null) {
e.target.form.article.value = Twinkle.warn.prev_article;
Twinkle.warn.prev_article = null;
}
e.target.form.article.notArticle = false;
Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, true);
Morebits.quickForm.resetElementLabel(e.target.form.article);
}
}
// add big red notice, warning users about how to use {{uw-[coi-]username}} appropriately
$('#tw-warn-red-notice').remove();
var $redWarning;
if (value === 'uw-username') {
$redWarning = $("<div style='color: red;' id='tw-warn-red-notice'>{{نام کاربری نامناسب}} <b>نباید</b> برای موارد نقض <b>آشکار</b> سیاستها استفاده شود. " + // localized
"نقضهای آشکار باید مستقیماً در [[وو:تامنام]] گزارش شوند (با استفاده از زبانهٔ بررسی و گزارش پیشرفتهٔ توینکل). " + // localized
'{{نام کاربری نامناسب}} باید تنها در موارد لبمرزی و جهت ورود به گفتگو با کاربر استفاده شود.</div>'); // localized
$redWarning.insertAfter(Morebits.quickForm.getElementLabelObject(e.target.form.reasonGroup));
} else if (value === 'uw-coi-username') {
$redWarning = $("<div style='color: red;' id='tw-warn-red-notice'>{{uw-coi-username}} <b>نباید</b> برای موارد نقض <b>آشکار</b>سیاست نام کاربری استفاده شود. " + // localized
"نقضهای آشکار باید مستقیماً در [[وو:تامنام]] گزارش شوند (با استفاده از زبانهٔ بررسی و گزارش پیشرفتهٔ توینکل). " + // localized
'{{uw-coi-username}} باید تنها در موارد لبمرزی و جهت ورود به گفتگو با کاربر استفاده شود.</div>'); // localized
$redWarning.insertAfter(Morebits.quickForm.getElementLabelObject(e.target.form.reasonGroup));
}
};
Twinkle.warn.callbacks = {
getWarningWikitext: function(templateName, article, reason, isCustom) {
var text = '{{subst:' + templateName;
// add linked article for user warnings
if (article) {
// c&pmove has the source as the first parameter
if (templateName === 'uw-c&pmove') {
text += '|to=' + article;
} else {
text += '|1=' + article;
}
}
if (reason && !isCustom) {
// add extra message
if (templateName === 'uw-csd' || templateName === 'uw-probation' ||
templateName === 'uw-userspacenoindex' || templateName === 'uw-userpage') {
text += "|3=''" + reason + "''";
} else {
text += "|2=''" + reason + "''";
}
}
text += '}}';
if (reason && isCustom) {
// we assume that custom warnings lack a {{{2}}} parameter
text += " ''" + reason + "''";
}
return text + ' ~~~~';
},
showPreview: function(form, templatename) {
// Provided on autolevel, not otherwise
templatename = templatename || form.sub_group.value;
var linkedarticle = form.article.value;
var templatetext;
templatetext = Twinkle.warn.callbacks.getWarningWikitext(templatename, linkedarticle,
form.reason.value, form.main_group.value === 'custom');
form.previewer.beginRender(templatetext, 'User_talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username
},
// Just a pass-through unless the autolevel option was selected
preview: function(form) {
if (form.main_group.value === 'autolevel') {
// Always get a new, updated talkpage for autolevel processing
var usertalk_page = new Morebits.wiki.page('بحث کاربر:' + mw.config.get('wgRelevantUserName'), 'بارگیری هشدارهای قبلی'); // localized
usertalk_page.setFollowRedirect(true);
usertalk_page.load(function(pageobj) {
Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj
var wikitext = pageobj.getPageText();
// history not needed for autolevel
var latest = Twinkle.warn.callbacks.dateProcessing(wikitext)[0];
var params = {
sub_group: form.sub_group.value,
article: form.article.value,
messageData: $(form.sub_group).find('option[value="' + $(form.sub_group).val() + '"]').data('messageData')
};
var template = Twinkle.warn.callbacks.autolevelParseWikitext(wikitext, params, latest)[0];
Twinkle.warn.callbacks.showPreview(form, template);
// If the templates have diverged, fake a change event
// to reload the menu with the updated pageobj
if (form.sub_group.value !== template) {
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
form.main_group.dispatchEvent(evt);
}
});
} else {
Twinkle.warn.callbacks.showPreview(form);
}
},
/**
* Used in the main and autolevel loops to determine when to warn
* about excessively recent, stale, or identical warnings.
* @param {string} wikitext The text of a user's talk page, from getPageText()
* @returns {Object[]} - Array of objects: latest contains most recent
* warning and date; history lists all prior warnings
*/
dateProcessing: function(wikitext) {
var history_re = /<!--\s?Template:([uU]w-.*?)\s?-->.*?(\d{1,2}:\d{1,2}, \d{1,2} \w+ \d{4} \(UTC\))/g;
var history = {};
var latest = { date: new Morebits.date(0), type: '' };
var current;
while ((current = history_re.exec(wikitext)) !== null) {
var template = current[1], current_date = new Morebits.date(current[2]);
if (!(template in history) || history[template].isBefore(current_date)) {
history[template] = current_date;
}
if (!latest.date.isAfter(current_date)) {
latest.date = current_date;
latest.type = template;
}
}
return [latest, history];
},
/**
* Main loop for deciding what the level should increment to. Most of
* this is really just error catching and updating the subsequent data.
* May produce up to two notices in a twinkle-warn-autolevel-messages div
*
* @param {string} wikitext The text of a user's talk page, from getPageText() (required)
* @param {Object} params Params object: sub_group is the template (required);
* article is the user-provided article (form.article) used to link ARV on recent level4 warnings;
* messageData is only necessary if getting the full template, as it's
* used to ensure a valid template of that level exists
* @param {Object} latest First element of the array returned from
* dateProcessing. Provided here rather than processed within to avoid
* repeated call to dateProcessing
* @param {(Date|Morebits.date)} date Date from which staleness is determined
* @param {Morebits.status} statelem Status element, only used for handling error in final execution
*
* @returns {Array} - Array that contains the full template and just the warning level
*/
autolevelParseWikitext: function(wikitext, params, latest, date, statelem) {
var template = params.sub_group.replace(/(.*)\d$/, '$1');
var level; // undefined rather than '' means the isNaN below will return true
if (/\d(?:im)?$/.test(latest.type)) { // level1-4im
level = parseInt(latest.type.replace(/.*(\d)(?:im)?$/, '$1'), 10);
} else if (latest.type) { // Non-numbered warning
// Try to leverage existing categorization of
// warnings, all but one are universally lowercased
var loweredType = /uw-multipleIPs/i.test(template) ? 'uw-multipleIPs' : template.toLowerCase();
// It would be nice to account for blocks, but in most
// cases the hidden message is terminal, not the sig
if (Twinkle.warn.messages.singlewarn[loweredType]) {
level = 3;
} else {
level = 1; // singlenotice or not found
}
}
var $autolevelMessage = $('<div/>', {'id': 'twinkle-warn-autolevel-message'});
if (isNaN(level)) { // No prior warnings found, this is the first
level = 1;
} else if (level > 4 || level < 1) { // Shouldn't happen
var message = 'Unable to parse previous warning level, please manually select a warning level.';
if (statelem) {
statelem.error(message);
} else {
alert(message);
}
return;
} else {
date = date || new Date();
var autoTimeout = new Morebits.date(latest.date.getTime()).add(parseInt(Twinkle.getPref('autolevelStaleDays'), 10), 'days');
if (autoTimeout.isAfter(date)) {
if (level === 4) {
level = 4;
// Basically indicates whether we're in the final Main evaluation or not,
// and thus whether we can continue or need to display the warning and link
if (!statelem) {
var $link = $('<a/>', {
'href': '#',
'text': 'برای باز کردن ابزار بررسی و هشدار پیشرفته اینجا کلیک کنید.', // localized
'css': { 'fontWeight': 'bold' },
'click': function() {
Morebits.wiki.actionCompleted.redirect = null;
Twinkle.warn.dialog.close();
Twinkle.arv.callback(mw.config.get('wgRelevantUserName'));
$('input[name=page]').val(params.article); // Target page
$('input[value=final]').prop('checked', true); // Vandalism after final
}
});
var statusNode = $('<div/>', {
'text': mw.config.get('wgRelevantUserName') + ' اخیراً یک هشدار سطح ۴ دریافت کردهاست (' + latest.type + ') بنابراین احتمالاً گزارش کاربر گزینهٔ بهتری است; ', // localized
'css': {'color': 'red' }
});
statusNode.append($link[0]);
$autolevelMessage.append(statusNode);
}
} else { // Automatically increase severity
level += 1;
}
} else { // Reset warning level if most-recent warning is too old
level = 1;
}
}
// Validate warning level, falling back to the uw-generic series.
// Only a few items are missing a level, and in all but a handful
// of cases, the uw-generic series is explicitly used elsewhere per WP:UTM.
if (params.messageData && !params.messageData['level' + level]) {
template = 'uw-generic';
}
template += level;
$autolevelMessage.prepend($('<div>Will issue a <span style="font-weight: bold;">level ' + level + '</span> template.</div>'));
// After the only other message: the (text-only) staleness note
$('#twinkle-warn-autolevel-message').remove(); // clean slate
$autolevelMessage.insertAfter($('#twinkle-warn-warning-message'));
return [template, level];
},
main: function(pageobj) {
var text = pageobj.getPageText();
var statelem = pageobj.getStatusElement();
var params = pageobj.getCallbackParameters();
var messageData = params.messageData;
// JS somehow didn't get destructured assignment until ES6 so of course IE doesn't support it
var warningHistory = Twinkle.warn.callbacks.dateProcessing(text);
var latest = warningHistory[0];
var history = warningHistory[1];
var now = new Morebits.date(pageobj.getLoadTime());
Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj, just in case
if (params.main_group === 'autolevel') {
// [template, level]
var templateAndLevel = Twinkle.warn.callbacks.autolevelParseWikitext(text, params, latest, now, statelem);
// Only if there's a change from the prior display/load
if (params.sub_group !== templateAndLevel[0] && !confirm('Will issue a {{' + templateAndLevel[0] + '}} template to the user, okay?')) {
statelem.error('aborted per user request');
return;
}
// Update params now that we've selected a warning
params.sub_group = templateAndLevel[0];
messageData = params.messageData['level' + templateAndLevel[1]];
} else if (params.sub_group in history) {
if (new Morebits.date(history[params.sub_group]).add(1, 'day').isAfter(now)) {
if (!confirm('یک ' + params.sub_group + ' همسان در ۲۴ ساعت گذشته صادر شدهاست. \nهمچنان مایل هستید این هشدار/اعلان را بیافزایید؟')) { // localized
statelem.error('به درخواست کاربر لغو شد'); // localized
return;
}
}
}
latest.date.add(1, 'minute'); // after long debate, one minute is max
if (latest.date.isAfter(now)) {
if (!confirm('A ' + latest.type + ' در یک دقیقهٔ گذشته صادر شدهاست. \nهمچنان مایل هستید این هشدار/اعلان را بیافزایید؟')) { // localized
statelem.error('به درخواست کاربر لغو شد'); // localized
return;
}
}
var dateHeaderRegex = now.monthHeaderRegex(), dateHeaderRegexLast, dateHeaderRegexResult;
while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) {
dateHeaderRegexResult = dateHeaderRegexLast;
}
// If dateHeaderRegexResult is null then lastHeaderIndex is never checked. If it is not null but
// \n== is not found, then the date header must be at the very start of the page. lastIndexOf
// returns -1 in this case, so lastHeaderIndex gets set to 0 as desired.
var lastHeaderIndex = text.lastIndexOf('\n==') + 1;
if (text.length > 0) {
text += '\n\n';
}
if (messageData.heading) {
text += '== ' + messageData.heading + ' ==\n';
} else if (!dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex) {
Morebits.status.info('Info', 'یک سرتیتر سطح ۲ برای تاریخ خواهد ساخت، زیرا هیچ سرتیتری برای این ماه یافت نشد'); // localized
text += now.monthHeader() + '\n';
}
text += Twinkle.warn.callbacks.getWarningWikitext(params.sub_group, params.article,
params.reason, params.main_group === 'custom');
if (Twinkle.getPref('showSharedIPNotice') && mw.util.isIPAddress(mw.config.get('wgTitle'))) {
Morebits.status.info('Info', 'افزودن اعلان آیپی مشترک'); // localized
text += '\n{{subst:آگاهی آیپی مشترک}}'; // localized
}
// build the edit summary
var summary;
if (params.main_group === 'custom') {
switch (params.sub_group.substr(-1)) {
case '1':
summary = 'یادآوری کلی'; // localized
break;
case '2':
summary = 'اخطار'; // localized
break;
case '3':
summary = 'هشدار'; // localized
break;
case '4':
summary = 'آخرین هشدار'; // localized
break;
case 'm':
if (params.sub_group.substr(-3) === '4im') {
summary = 'تنها هشدار'; // localized
break;
}
summary = 'اعلان'; // localized
break;
default:
summary = 'اعلان'; // localized
break;
}
summary += ': ' + Morebits.string.toUpperCaseFirstChar(messageData.label);
} else {
// Normalize kitchensink to the 1-4im style
if (params.main_group === 'kitchensink' && !/^D+$/.test(params.sub_group)) {
var sub = params.sub_group.substr(-1);
if (sub === 'm') {
sub = params.sub_group.substr(-3);
}
// Don't overwrite uw-3rr, technically unnecessary
if (/\d/.test(sub)) {
params.main_group = 'level' + sub;
}
}
summary = /^\D+$/.test(params.main_group) ? messageData.summary : messageData[params.main_group].summary;
if (messageData.suppressArticleInSummary !== true && params.article) {
if (params.sub_group === 'uw-agf-sock' ||
params.sub_group === 'uw-socksuspect' ||
params.sub_group === 'uw-aiv') { // these templates require a username
summary += ' of [[:کاربر:' + params.article + ']]'; // localized
} else {
summary += ' on [[:' + params.article + ']]';
}
}
}
summary += '.' + Twinkle.getPref('summaryAd');
pageobj.setPageText(text);
pageobj.setEditSummary(summary);
pageobj.setWatchlist(Twinkle.getPref('watchWarnings'));
pageobj.save();
}
};
Twinkle.warn.callback.evaluate = function twinklewarnCallbackEvaluate(e) {
var userTalkPage = 'بحث_کاربر:' + mw.config.get('wgRelevantUserName');
// First, check to make sure a reason was filled in if uw-username was selected
if (e.target.sub_group.value === 'uw-username' && e.target.article.value.trim() === '') {
alert('لازم است که برای الگوی {{نام کاربری نامناسب}} یک دلیل ارائه کنید.'); // localized
return;
}
// Find the selected <option> element so we can fetch the data structure
var selectedEl = $(e.target.sub_group).find('option[value="' + $(e.target.sub_group).val() + '"]');
// Then, grab all the values provided by the form
var params = {
reason: e.target.reason.value,
main_group: e.target.main_group.value,
sub_group: e.target.sub_group.value,
article: e.target.article.value, // .replace( /^(Image|Category):/i, ':$1:' ), -- apparently no longer needed...
messageData: selectedEl.data('messageData')
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(e.target);
Morebits.wiki.actionCompleted.redirect = userTalkPage;
Morebits.wiki.actionCompleted.notice = 'هشدار دادن کامل شد، بارگیری صفحهٔ بحث تا چند ثانیهٔ دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'User talk page modification');
wikipedia_page.setCallbackParameters(params);
wikipedia_page.setFollowRedirect(true);
wikipedia_page.load(Twinkle.warn.callbacks.main);
};
})(jQuery);
// </nowiki>
990twhh4zb45rdexflm1voyjunvacv1
کاربر:Mamouri/twinkle/twinklearv-2020.js
2
219486
908993
2026-07-30T16:04:09Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
908993
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** twinklearv.js: ARV module
****************************************
* Mode of invocation: Tab ("ARV")
* Active on: Any page with relevant user name (userspace, contribs, etc.)
*
* Localized for fa.wiktionary (ویکیواژه):
* - The only report venue is the admin noticeboard
* «ویکیواژه:تابلوی اعلانات مدیران», with the subpage
* «ویکیواژه:تابلوی اعلانات مدیران/نامهای کاربری نامناسب» for usernames.
* - SPI (sock/puppet) and AN3 (edit-warring) venues do not exist here and were removed.
*/
// Venue pages on fa.wiktionary
var ADMIN_NOTICEBOARD = 'ویکیواژه:تابلوی اعلانات مدیران';
var USERNAME_NOTICEBOARD = 'ویکیواژه:تابلوی اعلانات مدیران/نامهای کاربری نامناسب';
Twinkle.arv = function twinklearv() {
var username = mw.config.get('wgRelevantUserName');
if (!username || username === mw.config.get('wgUserName')) {
return;
}
var isIP = mw.util.isIPAddress(username);
var title = isIP ? 'گزارش نشانی آیپی به مدیران' : 'گزارش کاربر به مدیران';
Twinkle.addPortletLink(function() {
Twinkle.arv.callback(username, isIP);
}, 'ARV', 'tw-arv', title);
};
Twinkle.arv.callback = function (uid, isIP) {
var Window = new Morebits.simpleWindow(600, 500);
Window.setTitle('گزارش کاربر به مدیران');
Window.setScriptName('Twinkle');
Window.addFooterLink('تابلوی اعلانات مدیران', ADMIN_NOTICEBOARD);
var form = new Morebits.quickForm(Twinkle.arv.callback.evaluate);
var categories = form.append({
type: 'select',
name: 'category',
label: 'نوع گزارش را انتخاب کنید: ',
event: Twinkle.arv.callback.changeCategory
});
categories.append({
type: 'option',
label: 'خرابکاری / گزارش عمومی',
value: 'aiv'
});
categories.append({
type: 'option',
label: 'نام کاربری نامناسب',
value: 'username',
disabled: mw.util.isIPAddress(uid)
});
form.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-arv-blockwarning'
});
form.append({
type: 'field',
label: 'محل کار',
name: 'work_area'
});
form.append({ type: 'submit' });
form.append({
type: 'hidden',
name: 'uid',
value: uid
});
var result = form.render();
Window.setContent(result);
Window.display();
// Check if the user is blocked, update notice
var query = {
action: 'query',
list: 'blocks',
bkprop: 'range|flags',
format: 'json'
};
if (isIP) {
query.bkip = uid;
} else {
query.bkusers = uid;
}
new Morebits.wiki.api('بررسی وضعیت قطع دسترسی کاربر', query, function(apiobj) {
var blocklist = apiobj.getResponse().query.blocks;
if (blocklist.length) {
var block = blocklist[0];
var message = (isIP ? 'این نشانی آیپی' : 'این حساب') + ' هماکنون ' + (block.partial ? 'بهطور جزئی ' : '') + 'قطع دسترسی شده است';
// Start and end differ, range blocked
message += block.rangestart !== block.rangeend ? ' (بهعنوان بخشی از قطع دسترسی محدوده).' : '.';
if (block.partial) {
$('#twinkle-arv-blockwarning').css('color', 'black'); // Less severe
}
$('#twinkle-arv-blockwarning').text(message);
}
}).post();
// We must init the
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.category.dispatchEvent(evt);
};
Twinkle.arv.callback.changeCategory = function (e) {
var value = e.target.value;
var root = e.target.form;
var old_area = Morebits.quickForm.getElements(root, 'work_area')[0];
var work_area = null;
switch (value) {
case 'aiv':
/* falls through */
default:
work_area = new Morebits.quickForm.element({
type: 'field',
label: 'گزارش کاربر برای خرابکاری',
name: 'work_area'
});
work_area.append({
type: 'input',
name: 'page',
label: 'صفحهٔ مرتبط: ',
tooltip: 'برای پیوند ندادن به صفحه در گزارش، خالی بگذارید',
value: mw.util.getParamValue('vanarticle') || '',
event: function(e) {
var value = e.target.value;
var root = e.target.form;
if (value === '') {
root.badid.disabled = root.goodid.disabled = true;
} else {
root.badid.disabled = false;
root.goodid.disabled = root.badid.value === '';
}
}
});
work_area.append({
type: 'input',
name: 'badid',
label: 'شناسهٔ نسخهٔ صفحهٔ هدف هنگام خرابکاری: ',
tooltip: 'برای نبود پیوند تفاوت، خالی بگذارید',
value: mw.util.getParamValue('vanarticlerevid') || '',
disabled: !mw.util.getParamValue('vanarticle'),
event: function(e) {
var value = e.target.value;
var root = e.target.form;
root.goodid.disabled = value === '';
}
});
work_area.append({
type: 'input',
name: 'goodid',
label: 'شناسهٔ آخرین نسخهٔ سالم پیش از خرابکاری صفحهٔ هدف: ',
tooltip: 'برای پیوند تفاوت با نسخهٔ پیشین، خالی بگذارید',
value: mw.util.getParamValue('vanarticlegoodrevid') || '',
disabled: !mw.util.getParamValue('vanarticle') || mw.util.getParamValue('vanarticlerevid')
});
work_area.append({
type: 'checkbox',
name: 'arvtype',
list: [
{
label: 'خرابکاری پس از آخرین اخطار (سطح ۴ یا ۴im)',
value: 'final'
},
{
label: 'خرابکاری در کمتر از یک روز پس از پایان قطع دسترسی',
value: 'postblock'
},
{
label: 'آشکارا یک حساب صرفاً خرابکاری',
value: 'vandalonly',
disabled: mw.util.isIPAddress(root.uid.value)
},
{
label: 'حساب صرفاً برای تبلیغ',
value: 'promoonly',
disabled: mw.util.isIPAddress(root.uid.value)
},
{
label: 'حساب آشکارا یک ربات هرزنامه یا حساب هکشده است',
value: 'spambot'
}
]
});
work_area.append({
type: 'textarea',
name: 'reason',
label: 'توضیح: '
});
work_area = work_area.render();
old_area.parentNode.replaceChild(work_area, old_area);
break;
case 'username':
work_area = new Morebits.quickForm.element({
type: 'field',
label: 'گزارش نام کاربری نامناسب',
name: 'work_area'
});
work_area.append({
type: 'header',
label: 'نوع(های) نام کاربری نامناسب'
});
work_area.append({
type: 'checkbox',
name: 'arvtype',
list: [
{
label: 'نام کاربری گمراهکننده',
value: 'misleading',
tooltip: 'نامهای کاربری گمراهکننده تصور نادرستی دربارهٔ مشارکتکننده ایجاد میکنند؛ برای نمونه تصور اقتدار نابجا یا تصور حساب ربات.'
},
{
label: 'نام کاربری تبلیغاتی',
value: 'promotional',
tooltip: 'نامهای کاربری تبلیغاتی تبلیغ یک شرکت، وبگاه یا گروه هستند.'
},
{
label: 'نام کاربری که استفادهٔ اشتراکی را میرساند',
value: 'shared',
tooltip: 'نامهای کاربری که احتمال استفادهٔ اشتراکی را میرسانند (نام شرکتها یا گروهها) مجاز نیستند.'
},
{
label: 'نام کاربری توهینآمیز',
value: 'offensive',
tooltip: 'نامهای کاربری توهینآمیز ویرایش هماهنگ را دشوار یا ناممکن میکنند.'
},
{
label: 'نام کاربری مخل',
value: 'disruptive',
tooltip: 'نامهای کاربری مخل شامل حملههای شخصی یا نشاندهندهٔ قصد آشکار برای اخلال هستند.'
}
]
});
work_area.append({
type: 'textarea',
name: 'reason',
label: 'توضیح:'
});
work_area = work_area.render();
old_area.parentNode.replaceChild(work_area, old_area);
break;
}
};
Twinkle.arv.callback.evaluate = function(e) {
var form = e.target;
var reason = '';
var comment = '';
if (form.reason) {
comment = form.reason.value;
}
var uid = form.uid.value;
var types;
switch (form.category.value) {
// Report user for vandalism / general report
case 'aiv':
/* falls through */
default:
types = form.getChecked('arvtype');
if (!types.length && comment === '') {
alert('باید دلیلی مشخص کنید');
return;
}
types = types.map(function(v) {
switch (v) {
case 'final':
return 'خرابکاری پس از آخرین اخطار';
case 'postblock':
return 'خرابکاری پس از پایان اخیر قطع دسترسی';
case 'vandalonly':
return 'رفتار آشکارا نشاندهندهٔ حساب صرفاً خرابکاری است';
case 'promoonly':
return 'حساب تنها برای مقاصد تبلیغاتی استفاده میشود';
case 'spambot':
return 'حساب آشکارا یک ربات هرزنامه یا حساب هکشده است';
default:
return 'دلیل نامشخص';
}
}).join('؛ ');
if (form.page.value !== '') {
reason = 'در [[:' + form.page.value + ']]';
if (form.badid.value !== '') {
reason += ' ([[Special:Diff/' + form.goodid.value + '/' + form.badid.value + '|تفاوت]])';
}
reason += ':';
}
if (types) {
reason += ' ' + types;
}
if (comment !== '') {
reason += (reason === '' ? '' : '. ') + comment;
}
reason = reason.trim();
if (!/[.؟!؛]$/.test(reason)) {
reason += '.';
}
reason += ' ~~' + '~~';
reason = reason.replace(/\r?\n/g, '\n*:'); // indent newlines
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = ADMIN_NOTICEBOARD;
Morebits.wiki.actionCompleted.notice = 'گزارش انجام شد';
var aivPage = new Morebits.wiki.page(ADMIN_NOTICEBOARD, 'در حال پردازش گزارش');
aivPage.setFollowRedirect(true);
aivPage.load(function() {
var text = aivPage.getPageText();
// check if user has already been reported
if (text.indexOf('[[Special:Contributions/' + uid + '|') !== -1) {
aivPage.getStatusElement().error('این کاربر پیشتر گزارش شده است؛ گزارش تازهای افزوده نمیشود');
Morebits.status.printUserText(reason, 'متنی که نوشتید در زیر آمده است، در صورت تمایل میتوانید آن را بهصورت دستی زیر گزارش موجود بگذارید:');
return;
}
aivPage.getStatusElement().status('در حال افزودن گزارش تازه...');
aivPage.setEditSummary('گزارش [[Special:Contributions/' + uid + '|' + uid + ']].');
aivPage.setChangeTags(Twinkle.changeTags);
aivPage.setAppendText('\n* [[Special:Contributions/' + uid + '|' + uid + ']] – ' + reason);
aivPage.append();
});
break;
// Report inappropriate username
case 'username':
types = form.getChecked('arvtype').map(function(v) {
switch (v) {
case 'misleading':
return 'گمراهکننده';
case 'promotional':
return 'تبلیغاتی';
case 'shared':
return 'اشتراکی';
case 'offensive':
return 'توهینآمیز';
case 'disruptive':
return 'مخل';
default:
return v;
}
});
reason = '* [[Special:Contributions/' + uid + '|' + uid + ']] – ';
if (types.length) {
reason += 'نقض سیاست نام کاربری؛ نام کاربری ' + types.join('، ') + '. ';
}
if (comment !== '') {
reason += comment + '. ';
}
reason += '~~' + '~~';
reason = reason.replace(/\r?\n/g, '\n*:'); // indent newlines
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = USERNAME_NOTICEBOARD;
Morebits.wiki.actionCompleted.notice = 'گزارش انجام شد';
var uaaPage = new Morebits.wiki.page(USERNAME_NOTICEBOARD, 'در حال پردازش گزارش نام کاربری');
uaaPage.setFollowRedirect(true);
uaaPage.load(function() {
var text = uaaPage.getPageText();
// check if user has already been reported
if (text.indexOf('[[Special:Contributions/' + uid + '|') !== -1) {
uaaPage.getStatusElement().error('این کاربر پیشتر فهرست شده است.');
Morebits.status.printUserText(reason, 'متنی که نوشتید در زیر آمده است، در صورت تمایل میتوانید آن را بهصورت دستی زیر گزارش موجود بگذارید:');
return;
}
uaaPage.getStatusElement().status('در حال افزودن گزارش تازه...');
uaaPage.setEditSummary('گزارش [[Special:Contributions/' + uid + '|' + uid + ']].');
uaaPage.setChangeTags(Twinkle.changeTags);
uaaPage.setPageText(text + '\n' + reason);
uaaPage.save();
});
break;
}
};
Twinkle.addInitCallback(Twinkle.arv, 'arv');
})(jQuery);
// </nowiki>
hm68gz7v23a2lxfblhh1vgu0q2u4924
کاربر:Mamouri/twinkle/twinkleblock-2020.js
2
219487
908994
2026-07-30T16:04:16Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
908994
javascript
text/javascript
// <nowiki>
(function($) {
var api = new mw.Api(), relevantUserName;
var menuFormattedNamespaces = $.extend({}, mw.config.get('wgFormattedNamespaces'));
menuFormattedNamespaces[0] = '(مقاله)'; // localized
/*
****************************************
*** twinkleblock.js: Block module
****************************************
* Mode of invocation: Tab ("Block")
* Active on: Any page with relevant user name (userspace, contribs, etc.)
*/
Twinkle.block = function twinkleblock() {
// should show on Contributions or Block pages, anywhere there's a relevant user
if (Morebits.userIsSysop && mw.config.get('wgRelevantUserName')) {
Twinkle.addPortletLink(Twinkle.block.callback, 'بستن', 'tw-block', 'قطع دسترسی یک کاربر'); // localized
}
};
Twinkle.block.callback = function twinkleblockCallback() {
if (mw.config.get('wgRelevantUserName') === mw.config.get('wgUserName') &&
!confirm('شما در حال قطع دسترسی خودتان هستید! آیا مطمئنید که میخواهید ادامه دهید؟')) { // localized
return;
}
Twinkle.block.currentBlockInfo = undefined;
Twinkle.block.field_block_options = {};
Twinkle.block.field_template_options = {};
var Window = new Morebits.simpleWindow(650, 530);
// need to be verbose about who we're blocking
Window.setTitle('قطع دسترسی یا افزودن الگوی قطع دسترسی برای ' + mw.config.get('wgRelevantUserName')); // localized
Window.setScriptName('توینکل'); // localized
// Window.addFooterLink('Block templates', 'Template:Uw-block/doc/Block_templates'); // localized [removed]
Window.addFooterLink('سیاست قطع دسترسی', 'وو:بستن'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#block'); // localized
var form = new Morebits.quickForm(Twinkle.block.callback.evaluate);
var actionfield = form.append({
type: 'field',
label: 'نوع عمل' // localized
});
actionfield.append({
type: 'checkbox',
name: 'actiontype',
event: Twinkle.block.callback.change_action,
list: [
{
label: 'قطع دسترسی کاربر', // localized
value: 'قطع دسترسی', // localized
tooltip: 'با استفاده از گزینههای ارائه شده، دسترسی کاربر را قطع کنید. اگر قطع دسترسی موردی انتخاب نشود، قطع دسترسی کلی خواهد بود.', // localized
checked: true
},
{
label: 'قطع دسترسی موردی', // localized
value: 'partial',
tooltip: 'انجام یا ثبت قطع دسترسی موردی', // localized
checked: Twinkle.getPref('defaultToPartialBlocks')
},
{
label: 'افزودن الگوی قطع دسترسی به صفحهٔ بحث کاربر', // localized
value: 'template',
tooltip: 'اگر مدیری که قطع دسترسی را انجام داده فراموش کرده که الگوی قطع دسترسی را در صفحهٔ بحث کاربر بگذارد، یا شما حسابی را قطع دسترسی کردید بدون آن که الگوی مذکور را در صفحهٔ بحث کاربر بگذارید، میتوانید از این روش برای ثبت الگوی مناسب استفاده کنید.', // localized
checked: true
}
]
});
form.append({ type: 'field', label: 'تنظیم اولیه', name: 'field_preset' }); // localized
form.append({ type: 'field', label: 'گزینههای الگو', name: 'field_template_options' }); // localized
form.append({ type: 'field', label: 'گزینههای بستن', name: 'field_block_options' }); // localized
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
result.root = result;
Twinkle.block.fetchUserInfo(function() {
// clean up preset data (defaults, etc.), done exactly once, must be before Twinkle.block.callback.change_action is called
Twinkle.block.transformBlockPresets();
if (Twinkle.block.currentBlockInfo) {
Window.addFooterLink('باز کردن کاربر', 'Special:Unblock/' + mw.config.get('wgRelevantUserName'), true); // localized
}
// init the controls after user and block info have been fetched
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.actiontype[0].dispatchEvent(evt);
});
};
Twinkle.block.fetchUserInfo = function twinkleblockFetchUserInfo(fn) {
api.get({
format: 'json',
action: 'query',
list: 'blocks|users|logevents',
letype: 'block',
lelimit: 1,
bkusers: mw.config.get('wgRelevantUserName'),
ususers: mw.config.get('wgRelevantUserName'),
letitle: 'User:' + mw.config.get('wgRelevantUserName')
})
.then(function(data) {
var blockinfo = data.query.blocks[0],
userinfo = data.query.users[0];
Twinkle.block.isRegistered = !!userinfo.userid;
relevantUserName = Twinkle.block.isRegistered ? 'User:' + mw.config.get('wgRelevantUserName') : mw.config.get('wgRelevantUserName');
if (blockinfo) {
// handle frustrating system of inverted boolean values
blockinfo.disabletalk = blockinfo.allowusertalk === undefined;
blockinfo.hardblock = blockinfo.anononly === undefined;
Twinkle.block.currentBlockInfo = blockinfo;
}
Twinkle.block.hasBlockLog = !!data.query.logevents.length;
// Used later to check if block status changed while filling out the form
Twinkle.block.blockLogId = Twinkle.block.hasBlockLog ? data.query.logevents[0].logid : false;
if (typeof fn === 'function') {
return fn();
}
}, function(msg) {
Morebits.status.init($('div[name="currentblock"] span').last()[0]);
Morebits.status.warn('خطا در دریافت اطلاعات کاربر', msg); // localized
});
};
Twinkle.block.callback.saveFieldset = function twinkleblockCallbacksaveFieldset(fieldset) {
Twinkle.block[$(fieldset).prop('name')] = {};
$(fieldset).serializeArray().forEach(function(el) {
// namespaces and pages for partial blocks are overwritten
// here, but we're handling them elsewhere so that's fine
Twinkle.block[$(fieldset).prop('name')][el.name] = el.value;
});
};
Twinkle.block.callback.change_action = function twinkleblockCallbackChangeAction(e) {
var field_preset, field_template_options, field_block_options, $form = $(e.target.form);
// Make ifs shorter
var blockBox = $form.find('[name=actiontype][value=block]').is(':checked');
var templateBox = $form.find('[name=actiontype][value=template]').is(':checked');
var partial = $form.find('[name=actiontype][value=partial]');
var partialBox = partial.is(':checked');
var blockGroup = partialBox ? Twinkle.block.blockGroupsPartial : Twinkle.block.blockGroups;
partial.prop('disabled', !blockBox && !templateBox);
Twinkle.block.callback.saveFieldset($('[name=field_block_options]'));
Twinkle.block.callback.saveFieldset($('[name=field_template_options]'));
if (blockBox) {
field_preset = new Morebits.quickForm.element({ type: 'field', label: 'Preset', name: 'field_preset' });
field_preset.append({
type: 'select',
name: 'preset',
label: 'انتخاب یک تنظیم اولیه:', // localized
event: Twinkle.block.callback.change_preset,
list: Twinkle.block.callback.filtered_block_groups(blockGroup)
});
field_block_options = new Morebits.quickForm.element({ type: 'field', label: 'گزینههای قطع دسترسی', name: 'field_block_options' }); // localized
field_block_options.append({ type: 'div', name: 'hasblocklog', label: ' ' });
field_block_options.append({ type: 'div', name: 'currentblock', label: ' ' });
field_block_options.append({
type: 'select',
name: 'expiry_preset',
label: 'زمان سرآمدن:', // localized
event: Twinkle.block.callback.change_expiry,
list: [
{ label: 'سفارشی', value: 'custom', selected: true }, // localized
{ label: 'بیپایان', value: 'infinity' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۲۴ ساعت', value: '24 hours' }, // localized
{ label: '۳۱ ساعت', value: '31 hours' }, // localized
{ label: '۳۶ ساعت', value: '36 hours' }, // localized
{ label: '۴۸ ساعت', value: '48 hours' }, // localized
{ label: '۶۰ ساعت', value: '60 hours' }, // localized
{ label: '۷۲ ساعت', value: '72 hours' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۶ ماه', value: '6 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: '۲ سال', value: '2 years' }, // localized
{ label: '۳ سال', value: '3 years' } // localized
]
});
field_block_options.append({
type: 'input',
name: 'expiry',
label: 'زمان سرآمدن سفارشی', // localized
tooltip: 'میتوانید یک زمان نسبی به انگلیسی وارد کنید (مثلاً 1 minute یا 19 days) یا این که یک زمان مشخص را به صورت yyyymmddhhmm با ارقام انگلسی وارد کنید (مثلاً 200602011405 میشود ساعت ۱۴:۰۵ روز ۱ فوریهٔ ۲۰۰۶)', // localizde
value: Twinkle.block.field_block_options.expiry || Twinkle.block.field_template_options.template_expiry
});
if (partialBox) { // Partial block
field_block_options.append({
type: 'select',
multiple: true,
name: 'pagerestrictions',
label: 'صفحههای مورد قطع دسترسی', // localized
value: '',
tooltip: 'حداکثر ۱۰ مورد.' // localized
});
var ns = field_block_options.append({
type: 'select',
multiple: true,
name: 'namespacerestrictions',
label: 'فضاهای نام مورد قطع دسترسی', // localized
value: '',
tooltip: 'جلوگیری از ویرایش این فضاهای نام.', // localized
});
$.each(menuFormattedNamespaces, function(number, name) {
// Ignore -1: Special; -2: Media; and 2300-2303: Gadget (talk) and Gadget definition (talk)
if (number >= 0 && number < 830) {
ns.append({ type: 'option', label: name, value: number });
}
});
}
var blockoptions = [
{
checked: Twinkle.block.field_block_options.nocreate,
label: 'جلوگیری از ایجاد حساب', // localized
name: 'nocreate',
value: '1'
},
{
checked: Twinkle.block.field_block_options.noemail,
label: 'جلوگیری از ارسال ایمیل', // localized
name: 'noemail',
value: '1'
},
{
checked: Twinkle.block.field_block_options.disabletalk,
label: 'جلوگیری از ویرایش صفحهٔ بحث در مدت قطع دسترسی', // localized
name: 'disabletalk',
value: '1',
tooltip: partialBox ? 'در صورت اعمال قطع دسترسی موردی، این گزینه باید خاموش باشد مگر اینکه شما بخواهید از ویرایش کاربر در فضای نام بحث کاربر نیز جلوگیری کنید' : '' // localized
}
];
if (Twinkle.block.isRegistered) {
blockoptions.push({
checked: Twinkle.block.field_block_options.autoblock,
label: 'بستن خودکار نشانیهایآیپی', // localized
name: 'autoblock',
value: '1'
});
} else {
blockoptions.push({
checked: Twinkle.block.field_block_options.hardblock,
label: 'بستن حسابهای ثبتنام کرده از همین نشانی آیپی', // localized
name: 'hardblock',
value: '1'
});
}
blockoptions.push({
checked: Twinkle.block.field_block_options.watchuser,
label: 'بستن صفحههای کاربر و بحث کاربر', // localized
name: 'watchuser',
value: '1'
});
field_block_options.append({
type: 'checkbox',
name: 'blockoptions',
list: blockoptions
});
field_block_options.append({
type: 'textarea',
label: 'دلیل (برای سیاههٔ قطع دسترسی):', // localized
name: 'reason',
tooltip: 'لطفاً جزئیات مفیدی را به پیام پیشفرض اضافه کنید.', // localized
value: Twinkle.block.field_block_options.reason
});
/*
field_block_options.append({
type: 'div',
name: 'filerlog_label',
label: 'See also:',
style: 'display:inline-block;font-style:normal !important',
tooltip: 'Insert a "see also" message to indicate whether the filter log or deleted contributions played a role in the decision to block.'
});
field_block_options.append({
type: 'checkbox',
name: 'filter_see_also',
event: Twinkle.block.callback.toggle_see_alsos,
style: 'display:inline-block; margin-right:5px',
list: [
{
label: 'Filter log',
checked: false,
value: 'filter log'
}
]
});
field_block_options.append({
type: 'checkbox',
name: 'deleted_see_also',
event: Twinkle.block.callback.toggle_see_alsos,
style: 'display:inline-block',
list: [
{
label: 'Deleted contribs',
checked: false,
value: 'deleted contribs'
}
]
});
*/ // localized [removed]
if (Twinkle.block.currentBlockInfo) {
field_block_options.append({ type: 'hidden', name: 'reblock', value: '1' });
}
}
if (templateBox) {
field_template_options = new Morebits.quickForm.element({ type: 'field', label: 'گزینههای الگو', name: 'field_template_options' }); // localized
field_template_options.append({
type: 'select',
name: 'template',
label: 'Choose talk page template:',
event: Twinkle.block.callback.change_template,
list: Twinkle.block.callback.filtered_block_groups(blockGroup, true),
value: Twinkle.block.field_template_options.template
});
field_template_options.append({
type: 'input',
name: 'article',
display: 'none',
label: 'Linked page',
value: '',
tooltip: 'A page can be linked within the notice, perhaps if it was the primary target of disruption. Leave empty for no page to be linked.'
});
// Only visible if partial and not blocking
field_template_options.append({
type: 'input',
name: 'area',
display: 'none',
label: 'Area blocked from',
value: '',
tooltip: 'توضیح اختیاری صفحات یا مکانهای نام کاربر از ویرایش مسدود شده است.' // localized
});
if (!blockBox) {
field_template_options.append({
type: 'input',
name: 'template_expiry',
display: 'none',
label: 'Period of blocking: ',
value: '',
tooltip: 'مدت زمان مسدود شدن برای نمونه ، ۲۴ ساعت ، ۲ هفته ، نامشخص و غیره ...' // localized
});
}
field_template_options.append({
type: 'input',
name: 'block_reason',
label: '"You have been blocked for ..." ',
display: 'none',
tooltip: 'یک دلیل اختیاری برای جایگزینی دلیل عمومی پیش فرض. فقط برای الگوهای بستن عمومی موجود است.', // localized
value: Twinkle.block.field_template_options.block_reason
});
if (blockBox) {
field_template_options.append({
type: 'checkbox',
name: 'blank_duration',
list: [
{
label: 'قرار ندادن تاریخ انقضا در الگو', // localized
checked: Twinkle.block.field_template_options.blank_duration,
tooltip: 'به جای اینکه مدت زمان را اضافه کنید، الگوی بستن را بخوانید "شما به طور موقت مسدود شدهاید ..."' // localized
}
]
});
} else {
field_template_options.append({
type: 'checkbox',
list: [
{
label: 'جلوگیری از ویرایش صفحهٔ بحث', // localized
name: 'notalk',
checked: Twinkle.block.field_template_options.notalk,
tooltip: 'Make the block template state that the user\'s talk page access has been removed'
},
{
label: 'جلوگیری از ارسال ایمیل', // localized
name: 'noemail_template',
checked: Twinkle.block.field_template_options.noemail_template,
tooltip: 'If the area is not provided, make the block template state that the user\'s email access has been removed'
},
{
label: 'جلوگیری از ایجاد حساب کاربری', // localized
name: 'nocreate_template',
checked: Twinkle.block.field_template_options.nocreate_template,
tooltip: 'If the area is not provided, make the block template state that the user\'s ability to create accounts has been removed'
}
]
});
}
var $previewlink = $('<a id="twinkleblock-preivew-link">Preview</a>');
$previewlink.off('click').on('click', function() {
Twinkle.block.callback.preview($form[0]);
});
$previewlink.css({cursor: 'pointer'});
field_template_options.append({ type: 'div', id: 'blockpreview', label: [ $previewlink[0] ] });
field_template_options.append({ type: 'div', id: 'twinkleblock-previewbox', style: 'display: none' });
}
var oldfield;
if (field_preset) {
oldfield = $form.find('fieldset[name="field_preset"]')[0];
oldfield.parentNode.replaceChild(field_preset.render(), oldfield);
} else {
$form.find('fieldset[name="field_preset"]').hide();
}
if (field_block_options) {
oldfield = $form.find('fieldset[name="field_block_options"]')[0];
oldfield.parentNode.replaceChild(field_block_options.render(), oldfield);
$form.find('[name=pagerestrictions]').select2({
width: '100%',
placeholder: 'صفحات را برای بستن کاربر انتخاب کنید', // localized
language: {
errorLoading: function() {
return 'Incomplete or invalid search term';
}
},
maximumSelectionLength: 10, // Software limitation [[phab:T202776]]
minimumInputLength: 1, // prevent ajax call when empty
ajax: {
url: mw.util.wikiScript('api'),
dataType: 'json',
delay: 100,
data: function(params) {
var title = mw.Title.newFromText(params.term);
if (!title) {
return;
}
return {
'action': 'query',
'format': 'json',
'list': 'allpages',
'apfrom': title.title,
'apnamespace': title.namespace,
'aplimit': '10'
};
},
processResults: function(data) {
return {
results: data.query.allpages.map(function(page) {
var title = mw.Title.newFromText(page.title, page.ns).toText();
return {
id: title,
text: title
};
})
};
}
},
templateSelection: function(choice) {
return $('<a>').text(choice.text).attr({
href: mw.util.getUrl(choice.text),
target: '_blank'
});
}
});
$form.find('[name=namespacerestrictions]').select2({
width: '100%',
matcher: Morebits.select2.matchers.wordBeginning,
language: {
searching: Morebits.select2.queryInterceptor
},
templateResult: Morebits.select2.highlightSearchMatches,
placeholder: 'Select namespaces to block user from'
});
mw.util.addCSS(
// prevent dropdown from appearing behind the dialog, just in case
'.select2-container { z-index: 10000; }' +
// Reduce padding
'.select2-results .select2-results__option { padding-top: 1px; padding-bottom: 1px; }' +
// Adjust font size
'.select2-container .select2-dropdown .select2-results { font-size: 13px; }' +
'.select2-container .selection .select2-selection__rendered { font-size: 13px; }' +
// Remove black border
'.select2-container--default.select2-container--focus .select2-selection--multiple { border: 1px solid #aaa; }' +
// Make the tiny cross larger
'.select2-selection__choice__remove { font-size: 130%; }'
);
} else {
$form.find('fieldset[name="field_block_options"]').hide();
// Clear select2 options
$form.find('[name=pagerestrictions]').val(null).trigger('change');
$form.find('[name=namespacerestrictions]').val(null).trigger('change');
}
if (field_template_options) {
oldfield = $form.find('fieldset[name="field_template_options"]')[0];
oldfield.parentNode.replaceChild(field_template_options.render(), oldfield);
e.target.form.root.previewer = new Morebits.wiki.preview($(e.target.form.root).find('#twinkleblock-previewbox').last()[0]);
} else {
$form.find('fieldset[name="field_template_options"]').hide();
}
if (Twinkle.block.hasBlockLog) {
var $blockloglink = $('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: mw.config.get('wgRelevantUserName'), type: 'block'}) + '">block log</a>)');
Morebits.status.init($('div[name="hasblocklog"] span').last()[0]);
Morebits.status.warn('This user has been blocked in the past', $blockloglink[0]);
}
if (Twinkle.block.currentBlockInfo) {
Morebits.status.init($('div[name="currentblock"] span').last()[0]);
// list=blocks without bkprops (as we do in fetchUerInfo)
// returns partial: '' if the user is partially blocked
var statusStr = relevantUserName + ' is ' + (Twinkle.block.currentBlockInfo.partial === '' ? 'partially blocked' : 'blocked sitewide');
if (Twinkle.block.currentBlockInfo.expiry === 'infinity') {
statusStr += ' (indef)';
} else if (new Morebits.date(Twinkle.block.currentBlockInfo.expiry).isValid()) {
statusStr += ' (expires ' + new Morebits.date(Twinkle.block.currentBlockInfo.expiry).calendar('utc') + ')';
}
var infoStr = 'برای تغییر مسدود شدن سوال کنید'; // localized
if (Twinkle.block.currentBlockInfo.partial === undefined && partialBox) {
infoStr += ', converting to a partial block';
} else if (Twinkle.block.currentBlockInfo.partial === '' && !partialBox) {
infoStr += ', converting to a sitewide block';
}
Morebits.status.warn(statusStr, infoStr);
Twinkle.block.callback.update_form(e, Twinkle.block.currentBlockInfo);
}
if (templateBox) {
// make sure all the fields are correct based on defaults
if (blockBox) {
Twinkle.block.callback.change_preset(e);
} else {
Twinkle.block.callback.change_template(e);
}
}
};
/*
* Keep alphabetized by key name, Twinkle.block.blockGroups establishes
* the order they will appear in the interface
*
* Block preset format, all keys accept only 'true' (omit for false) except where noted:
* <title of block template> : {
* autoblock: <autoblock any IP addresses used (for registered users only)>
* disabletalk: <disable user from editing their own talk page while blocked>
* expiry: <string - expiry timestamp, can include relative times like "5 months", "2 weeks" etc>
* forAnonOnly: <show block option in the interface only if the relevant user is an IP>
* forRegisteredOnly: <show block option in the interface only if the relevant user is registered>
* label: <string - label for the option of the dropdown in the interface (keep brief)>
* noemail: prevent the user from sending email through Special:Emailuser
* pageParam: <set if the associated block template accepts a page parameter>
* prependReason: <string - prepends the value of 'reason' to the end of the existing reason, namely for when revoking talk page access>
* nocreate: <block account creation from the user's IP (for anonymous users only)>
* nonstandard: <template does not conform to stewardship of WikiProject User Warnings and may not accept standard parameters>
* reason: <string - block rationale, as would appear in the block log,
* and the edit summary for when adding block template, unless 'summary' is set>
* reasonParam: <set if the associated block template accepts a reason parameter>
* sig: <string - set to ~~~~ if block template does not accept "true" as the value, or set null to omit sig param altogether>
* summary: <string - edit summary for when adding block template to user's talk page, if not set, 'reason' is used>
* suppressArticleInSummary: <set to suppress showing the article name in the edit summary, as with attack pages>
* templateName: <string - name of template to use (instead of key name), entry will be omitted from the Templates list.
* (e.g. use another template but with different block options)>
* useInitialOptions: <when preset is chosen, only change given block options, leave others as they were>
*
* WARNING: 'anononly' and 'allowusertalk' are enabled by default.
* To disable, set 'hardblock' and 'disabletalk', respectively
*/
Twinkle.block.blockPresetsInfo = {
'anonblock': {
expiry: '31 hours',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{anonblock}}',
sig: '~~~~'
},
'anonblock - school': {
expiry: '36 hours',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{anonblock}} <!-- Likely a school based on behavioral evidence -->',
templateName: 'anonblock',
sig: '~~~~'
},
'blocked proxy': {
expiry: '1 year',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
hardblock: true,
reason: '{{پروکسی باز}}', // localized
sig: null
},
'CheckUser block': {
expiry: '1 week',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{قطع دسترسی بازرسی کاربر}}', // localized
sig: '~~~~'
},
'checkuserblock-account': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{بندایش بازرسی کاربر-حساب کاربری}}', // localized
sig: '~~~~'
},
'checkuserblock-wide': {
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{checkuserblock-wide}}',
sig: '~~~~'
},
'colocationwebhost': {
expiry: '1 year',
forAnonOnly: true,
nonstandard: true,
reason: '{{میزبانیوبسرور}}', // localized
sig: null
},
'oversightblock': {
autoblock: true,
expiry: 'infinity',
nocreate: true,
nonstandard: true,
reason: '{{OversightBlock}}',
sig: '~~~~'
},
'school block': {
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{school block}}',
sig: '~~~~'
},
'spamblacklistblock': {
forAnonOnly: true,
expiry: '1 month',
disabletalk: true,
nocreate: true,
reason: '{{spamblacklistblock}} <!-- editor only attempts to add blacklisted links, see [[Special:Log/spamblacklist]] -->'
},
// Placeholder for when we add support for rangeblocks
// 'rangeblock' : {
// reason: '{{rangeblock}}',
// nocreate: true,
// nonstandard: true,
// forAnonOnly: true,
// sig: '~~~~'
// },
'tor': {
expiry: '1 year',
forAnonOnly: true,
nonstandard: true,
reason: '{{Tor}}',
sig: null
},
'webhostblock': {
expiry: '1 year',
forAnonOnly: true,
nonstandard: true,
reason: '{{میزبان وب بستهشده}}', // localized
sig: null
},
// uw-prefixed
'uw-3block': {
autoblock: true,
expiry: '24 hours',
nocreate: true,
pageParam: true,
reason: 'نقض[[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]]', // localized
summary: 'برای نقض [[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]] شما از ویرایش منع شدهاید' // localized
},
'uw-ablock': {
autoblock: true,
expiry: '31 hours',
forAnonOnly: true,
nocreate: true,
pageParam: true,
reasonParam: true,
summary: 'آدرس IP شما از ویرایش منع شده است', // localized
suppressArticleInSummary: true
},
'uw-adblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: 'استفاده از ویکیواژه به عنوان [[ویکیواژه:هرزنامه|هرزنامه]] یا [[ویکیواژه:ویکیواژه چه چیزی نیست|تبلیغات]] ', // localized
summary: 'شما به دلیل [[ویکیواژه:ویکیواژه چه چیزی نیست|ویکیواژه چه چیزی نیست]] از ویرایش منع شدهاید' // localized
},
'uw-aeblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: '[[WP:Arbitration enforcement|Arbitration enforcement]]',
reasonParam: true,
summary: 'شما به دلیل نقض [[WP:Arbitration|تصمیم داوری]] از ویرایش منع شده اید' // localized
},
'uw-bioblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: 'نقض سیایت شرح حال [[ویکیواژه:زندگینامه زندگان|زندگینامه زندگان]] ', // localized
summary: 'You have been blocked from editing for violations of Wikipedia\'s [[ویکیواژه:زندگینامه زندگان]]'
},
'uw-block': {
autoblock: true,
expiry: '24 hours',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reasonParam: true,
summary: 'شما از ویرایش منع شدهاید', // localized
suppressArticleInSummary: true
},
'uw-blockindef': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reasonParam: true,
summary: 'شما به طور نامحدود از ویرایش منع شدهاید', // localized
suppressArticleInSummary: true
},
'uw-blocknotalk': {
disabletalk: true,
pageParam: true,
reasonParam: true,
summary: 'شما از ویرایش منع شدهاید و دسترسی به صفحه بحث کاربر غیرفعال شده است', // localized
suppressArticleInSummary: true
},
'uw-botblock': {
forRegisteredOnly: true,
pageParam: true,
reason: 'اجرای اسکریپت [[ویکیواژه:ربات|ربات]] بدون [[ویکیواژه:سیاست رباترانی/درخواست مجوز|مجوز]]', // localized
summary: 'You have been blocked from editing because it appears you are running a [[ویکیواژه:ربات]] without [[ویکیواژه:سیاست رباترانی/درخواست مجوز]]' // localized
},
'uw-botublock': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-botublock}} <!-- Username implies a bot, soft block -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما نشان می دهد که این یک حساب [[ویکیواژه:ربات|ربات]] است ، که در حال حاضر تأیید نشده است.' // localized
},
'uw-causeblock': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-causeblock}} <!-- Username represents a non-profit, soft block -->',
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما این تصور را ایجاد میکند که این حساب نماینده یک گروه، سازمان یا وب سایت است' // localized
},
'uw-compblock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: 'امنیت حساب کاربری', // localized
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا اعتقاد بر این است که [[ویکیواژه:امنیت حساب کاربری|امنیت حساب شما]] به خطر افتادهاست.' // localized
},
'uw-copyrightblock': {
autoblock: true,
expiry: '24 hours',
nocreate: true,
pageParam: true,
reason: '[[WP:Copyright violations|Copyright violations]]',
summary: 'برای ادامه [[WP:COPYVIO|نقض حق نسخه برداری]] از ویرایش منع شدهاید' // localized
},
'uw-dblock': {
autoblock: true,
nocreate: true,
reason: 'حذف مدام محتوا', // localized
pageParam: true,
summary: 'به دلیل [[ویکیواژه:خرابکاری|خرابکاری]] از ویرایش منع شدهاید' // localized
},
'uw-disruptblock': {
autoblock: true,
nocreate: true,
reason: '[[ویکیواژه:ویرایش اخلالگرانه|ویرایش اخلالگرانه]]', // localized
summary: 'به دلیل [[ویکیواژه:ویرایش اخلالگرانه|ویرایش اخلالگرانه]] از ویرایش منع شدهاید' // localized
},
'uw-efblock': {
autoblock: true,
nocreate: true,
reason: 'Deliberately triggering the [[ویکیواژه:پالایه ویرایش]]', // localized
summary: 'شما به دلیل ویرایشهای مخرب که مرتباً باعث [[ویکیواژه:پالایه ویرایش|پالایه ویرایش]] شدهاند، از ویرایش منع شدهاید.' // localized
},
'uw-ewblock': {
autoblock: true,
expiry: '24 hours',
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]]', // localized
summary: 'برای جلوگیری از [[ویکیواژه:ویرایش اخلالگرانه|اختلال]] بیشتر ناشی از درگیری شما در [[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]]، از ویرایش منع شدهاید.' // localized
},
'uw-hblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:حمله شخصی ممنوع|حمله شخصی ممنوع]] یا [[ویکیواژه:آزار و اذیت|آزار و اذیت]]', // localized
summary: 'شما به دلیل [[ویکیواژه:آزار و اذیت|آزار و اذیت]] سایر کاربران از ویرایش منع شدهاید.'
},
'uw-ipevadeblock': {
forAnonOnly: true,
nocreate: true,
reason: '[[ویکیواژه:قطع دسترسی|قطع دسترسی]]', // localized
summary: 'آدرس آیپی شما به دلیل [[ویکیواژه:قطع دسترسی|قطع دسترسی]] از ویرایش منع شدهاست.' // localized
},
'uw-lblock': {
autoblock: true,
expiry: 'infinity',
nocreate: true,
reason: 'Making [[ویکیواژه:تهدید قانونی ممنوع|تهدید قانونی ممنوع]]', // localized
summary: 'شما به دلیل [[ویکیواژه:تهدید قانونی ممنوع|تهدید قانونی ممنوع]] از ویرایش منع شدهاید.' // localized
},
'uw-nothereblock': {
autoblock: true,
expiry: 'infinity',
nocreate: true,
reason: 'روشن است که [[ویکیواژه:ما برای ساختن دانشنامه اینجا هستیم|برای ساختن دانشنامه اینجا نیستید.]]', // localized
forRegisteredOnly: true,
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا به نظر میرسد که شما در اینجا برای [[ویکیواژه:ما برای ساختن دانشنامه اینجا هستیم|ساختن دانشنامه]] نیستید' // localized
},
'uw-npblock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: 'ایجاد مزخرفات ثبت اختراع یا سایر [[ویکیواژه:بیمعنی|صفحات نامناسب]]', // localized
summary: 'به دلیل ایجاد صفحات [[ویکیواژه:بیمعنی|بیمعنی]] از ویرایش منع شدهاید.' // localized
},
'uw-pablock': {
autoblock: true,
expiry: '31 hours',
nocreate: true,
reason: '[[ویکیواژه:حمله شخصی ممنوع|]] یا [[ویکیواژه:آزار و اذیت|]]', // localized
summary: 'شما به دلیل انجام [[ویکیواژه:حمله شخصی ممنوع|حملات شخصی]] به سایر کاربران از ویرایش منع شدهاید.' // localized
},
'uw-sblock': {
autoblock: true,
nocreate: true,
reason: 'استفاده از ویکیواژه به عنوان [[ویکیواژه:هرزنامه|هرزنامه]]', // localized
summary: 'به دلیل استفاده از ویکیواژه [[ویکیواژه:هرزنامه|هرزنامه]] از ویرایش منع شدهاید.' // localized
},
'uw-soablock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:هرزنامه]] / [[ویکیواژه:ویکیواژه چه چیزی نیست]]-تنها حساب', // localized
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا حساب شما فقط برای ایجاد [[ویکیواژه:هرزنامه|هرزنامه]] استفاده میشود.' // localized
},
'uw-socialmediablock': {
autoblock: true,
nocreate: true,
pageParam: true,
reason: 'استفاده از ویکیواژه به عنوان [[ویکیواژه:ویکیواژه چه چیزی نیست|یک وبلاگ، میزبان وب، سایت یا شبکههای اجتماعی]]', // localized
summary: 'شما به دلیل استفاده از صفحات کاربر و / یا مقاله به عنوان [[ویکیواژه:ویکیواژه چه چیزی نیست|وبلاگ، میزبان وب، سایت یا شبکههای اجتماعی]] از ویرایش منع شدهاید.' // localized
},
'uw-sockblock': {
autoblock: true,
forRegisteredOnly: true,
nocreate: true,
reason: 'سوءاستفاده از [[ویکیواژه:حساب زاپاس|چند حساب کاربری]]', // localized
summary: 'شما به دلیل سوء استفاده از [[ویکیواژه:حساب زاپاس|چند حساب کاربری]] ویرایش منع شدهاید ' // localized
},
'uw-softerblock': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-softerblock}} <!-- Promotional username, soft block -->',
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما این تصور را ایجاد میکند که این حساب نماینده یک گروه، سازمان یا وب سایت است' // localized
},
'uw-spamublock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: '{{Uw-spamublock}} <!-- Promotional username, promotional edits -->',
summary: 'شما بهطور نامحدود از ویرایش منع شدهاید زیرا حساب شما فقط برای [[ویکیواژه:هرزنامه|هرزنامه یا تبلیغات]] استفاده میشود و [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما نقض خط مشی نام کاربری است.' // localized
},
'uw-spoablock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: '[[ویکیواژه:حساب زاپاس|حساب زاپاس]]', // localized
summary: 'این حساب به عنوان یک [[ویکیواژه:حساب زاپاس|حساب زاپاس]] ایجاد شده و براساس نقض خط مشی ویکی پد مسدود شده است.' // localized
},
'uw-talkrevoked': {
disabletalk: true,
reason: 'لغو دسترسی به صفحه بحث: استفاده نامناسب از صفحه بحث کاربر در حالی که مسدود شدهاست.', // localized
prependReason: true,
summary: 'دسترسی به صفحه گفتگوی کاربر غیرفعال شدهاست', // localized
useInitialOptions: true
},
'uw-ublock': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-ublock}} <!-- Username violation, soft block -->',
reasonParam: true,
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] ناقض قوانین است.' // localized
},
'uw-ublock-double': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{Uw-ublock-double}} <!-- Username closely resembles another user, soft block -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما بیش از حد به نام کاربری کاربر ویکیواژه دیگر شباهت دارد.' // localized
},
'uw-ucblock': {
autoblock: true,
expiry: '31 hours',
nocreate: true,
pageParam: true,
reason: ' [[راهنما:مقدمهای بر ارجاع/۱|افزودن محتوای بدون منبع]]', // localized
summary: 'به دلیل افزودن مداوم از [[راهنما:مقدمهای بر ارجاع/۱|محتوای بدون منبع]]، شما از ویرایش منع شدهاید.' // localized
},
'uw-uhblock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: '{{Uw-uhblock}} <!-- Username violation, hard block -->',
reasonParam: true,
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما نقض آشکار خط مشی نام کاربری است.' // localized
},
'uw-ublock-wellknown': {
expiry: 'infinity',
forRegisteredOnly: true,
reason: '{{بستن نام واقعی}} <!-- Username represents a well-known person, soft block -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما با نام یک فرد شناخته شده زنده مطابقت دارد.' // localized
},
'uw-uhblock-double': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
reason: '{{Uw-ublock-double}} <!-- Username closely resembles another user, hard block -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا به نظر می رسد [[ویکیواژه:سیاست نام کاربری|نام کاربری]] شما نام یک کاربر دیگر ویکی پدیا را جعل میکند' // localized
},
'uw-upeblock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:فاشسازی مشارکت در برابر دستمزد]] in violation of the WMF [[WP:TOU|Terms of Use]]',
summary: 'شما به طور نامحدود به دلیل [[ویکیواژه:فاشسازی مشارکت در برابر دستمزد|فاشسازی مشارکت در برابر دستمزد]] از ویرایش منع شدهاید.' // localized
},
'uw-vaublock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reason: '{{Uw-vaublock}} <!-- Username violation, vandalism-only account -->',
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا [[ویکیواژه:حساب ساختهشده برای خرابکاری|حساب شما]] برای خرابکاری ایجاد شدهاست و ناقض [[ویکیواژه:سیاست نام کاربری|سیاست نام کاربری]] است.' // localized
},
'uw-vblock': {
autoblock: true,
expiry: '31 hours',
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:خرابکاری|خرابکاری]]', // localized
summary: 'برای جلوگیری از بیشتر [[ویکیواژه:خرابکاری|خرابکاری]] شما از ویرایش منع شدهاید.' // localized
},
'uw-voablock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: true,
reason: '[[ویکیواژه:حساب ساختهشده برای خرابکاری|حساب ساختهشده برای خرابکاری]]', // localized
summary: 'شما به طور نامحدود از ویرایش منع شدهاید زیرا حساب شما تنها برای [[ویکیواژه:حساب ساختهشده برای خرابکاری|خرابکاری]] استفاده میشود.' // localized
},
'zombie proxy': {
expiry: '1 month',
forAnonOnly: true,
nocreate: true,
nonstandard: true,
reason: '{{زامبی پراکسی}}', // localized
sig: null
},
// Begin partial block templates, accessed in Twinkle.block.blockGroupsPartial
'uw-acpblock': {
autoblock: true,
expiry: '48 hours',
nocreate: true,
pageParam: false,
reasonParam: true,
reason: 'Misusing [[ویکیواژه:حساب زاپاس|حساب زاپاس]]', // localized
summary: 'به دلیل سوء استفاده از [[ویکیواژه:حساب زاپاس|چندین حساب]] از [[ویکیواژه:قطع دسترسی موردی|ایجاد حساب کاربری]] منع شدهاید.' // localized
},
'uw-acpblockindef': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: true,
pageParam: false,
reasonParam: true,
reason: 'سوء استفاده از [[ویکیواژه:حساب زاپاس|حساب زاپاس]]', // localized
summary: 'به دلیل سوء استفاده از [[ویکیواژه:حساب زاپاس|چندین حساب]] از [[ویکیواژه:قطع دسترسی موردی|ایجاد حساب کاربری]] منع شدهاید.' // localized
},
'uw-aepblock': {
autoblock: true,
nocreate: false,
pageParam: false,
reason: '[[WP:Arbitration enforcement|Arbitration enforcement]]',
reasonParam: true,
summary: 'به دلیل [[WP:Arbitration|نقض تصمیم داوری]] از ویرایش [[ویکیواژه:قطع دسترسی موردی|منع]] شده اید.' // localized
},
'uw-epblock': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: false,
noemail: true,
pageParam: false,
reasonParam: true,
reason: '[[ویکیواژه:آزار و اذیت]] ایمیلی', // localized
summary: 'شما از ارسال ایمیل به ویرایشگران دیگر به دلیل [[ویکیواژه:آزار و اذیت|آزار و اذیت]] [[ویکیواژه:قطع دسترسی موردی|منع]] شدهاید' // localized
},
'uw-ewpblock': {
autoblock: true,
expiry: '24 hours',
nocreate: false,
pageParam: false,
reasonParam: true,
reason: '[[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]]', // localized
summary: 'شما از ویرایش مناطق خاصی از دانشنامه [[ویکیواژه:قطع دسترسی موردی|منع]] شدهاید تا از [[ویکیواژه:ویرایش اخلالگرانه|اختلال]] بیشتر ناشی از [[ویکیواژه:جنگ ویرایشی|جنگ ویرایشی]] جلوگیری شود.' // localized
},
'uw-pblock': {
autoblock: true,
expiry: '24 hours',
nocreate: false,
pageParam: false,
reasonParam: true,
summary: 'شما از ویرایش بخشهایی از دانشنامه [[ویکیواژه:قطع دسترسی مورد|منع]] شدهاید.' // localized
},
'uw-pblockindef': {
autoblock: true,
expiry: 'infinity',
forRegisteredOnly: true,
nocreate: false,
pageParam: false,
reasonParam: true,
summary: 'شما از ویرایش بخشهایی از دانشنامه [[ویکیواژه:قطع دسترسی مورد|منع]] شدهاید.' // localized
}
};
Twinkle.block.transformBlockPresets = function twinkleblockTransformBlockPresets() {
// supply sensible defaults
$.each(Twinkle.block.blockPresetsInfo, function(preset, settings) {
settings.summary = settings.summary || settings.reason;
settings.sig = settings.sig !== undefined ? settings.sig : 'yes';
// despite this it's preferred that you use 'infinity' as the value for expiry
settings.indefinite = settings.indefinite || settings.expiry === 'infinity' || settings.expiry === 'infinite' || settings.expiry === 'indefinite' || settings.expiry === 'never';
if (!Twinkle.block.isRegistered && settings.indefinite) {
settings.expiry = '31 hours';
} else {
settings.expiry = settings.expiry || '31 hours';
}
Twinkle.block.blockPresetsInfo[preset] = settings;
});
};
// These are the groups of presets and defines the order in which they appear. For each list item:
// label: <string, the description that will be visible in the dropdown>
// value: <string, the key of a preset in blockPresetsInfo>
Twinkle.block.blockGroups = [
{
label: 'Common block reasons',
list: [
{ label: 'anonblock', value: 'anonblock' },
{ label: 'anonblock - likely a school', value: 'anonblock - school' },
{ label: 'school block', value: 'school block' },
{ label: 'Generic block (custom reason)', value: 'uw-block' }, // ends up being default for registered users
{ label: 'Generic block (custom reason) - IP', value: 'uw-ablock', selected: true }, // set only when blocking IP
{ label: 'Generic block (custom reason) - indefinite', value: 'uw-blockindef' },
{ label: 'Disruptive editing', value: 'uw-disruptblock' },
{ label: 'Inappropriate use of user talk page while blocked', value: 'uw-talkrevoked' },
{ label: 'Not here to build an encyclopedia', value: 'uw-nothereblock' },
{ label: 'Unsourced content', value: 'uw-ucblock' },
{ label: 'Vandalism', value: 'uw-vblock' },
{ label: 'Vandalism-only account', value: 'uw-voablock' }
]
},
{
label: 'Extended reasons',
list: [
{ label: 'Advertising', value: 'uw-adblock' },
{ label: 'Arbitration enforcement', value: 'uw-aeblock' },
{ label: 'Block evasion - IP', value: 'uw-ipevadeblock' },
{ label: 'BLP violations', value: 'uw-bioblock' },
{ label: 'Copyright violations', value: 'uw-copyrightblock' },
{ label: 'Creating nonsense pages', value: 'uw-npblock' },
{ label: 'Edit filter-related', value: 'uw-efblock' },
{ label: 'Edit warring', value: 'uw-ewblock' },
{ label: 'Generic block with talk page access revoked', value: 'uw-blocknotalk' },
{ label: 'Harassment', value: 'uw-hblock' },
{ label: 'Legal threats', value: 'uw-lblock' },
{ label: 'Personal attacks or harassment', value: 'uw-pablock' },
{ label: 'Possible compromised account', value: 'uw-compblock' },
{ label: 'Removal of content', value: 'uw-dblock' },
{ label: 'Sock puppetry (master)', value: 'uw-sockblock' },
{ label: 'Sock puppetry (puppet)', value: 'uw-spoablock' },
{ label: 'Social networking', value: 'uw-socialmediablock' },
{ label: 'Spam', value: 'uw-sblock' },
{ label: 'Spam/advertising-only account', value: 'uw-soablock' },
{ label: 'Unapproved bot', value: 'uw-botblock' },
{ label: 'Undisclosed paid editing', value: 'uw-upeblock' },
{ label: 'Violating the three-revert rule', value: 'uw-3block' }
]
},
{
label: 'Username violations',
list: [
{ label: 'Bot username', value: 'uw-botublock' },
{ label: 'Promotional username, hard block', value: 'uw-spamublock' },
{ label: 'Promotional username, soft block', value: 'uw-softerblock' },
{ label: 'Similar username soft block', value: 'uw-ublock-double' },
{ label: 'Username violation, soft block', value: 'uw-ublock' },
{ label: 'Username violation, hard block', value: 'uw-uhblock' },
{ label: 'Username impersonation hard block', value: 'uw-uhblock-double' },
{ label: 'Username represents a well-known person, soft block', value: 'uw-ublock-wellknown' },
{ label: 'Username represents a non-profit, soft block', value: 'uw-causeblock' },
{ label: 'Username violation, vandalism-only account', value: 'uw-vaublock' }
]
},
{
label: 'Templated reasons',
list: [
{ label: 'blocked proxy', value: 'blocked proxy' },
{ label: 'CheckUser block', value: 'CheckUser block', disabled: !Morebits.userIsInGroup('checkuser') },
{ label: 'checkuserblock-account', value: 'checkuserblock-account', disabled: !Morebits.userIsInGroup('checkuser') },
{ label: 'checkuserblock-wide', value: 'checkuserblock-wide', disabled: !Morebits.userIsInGroup('checkuser') },
{ label: 'colocationwebhost', value: 'colocationwebhost' },
{ label: 'oversightblock', value: 'oversightblock', disabled: !Morebits.userIsInGroup('oversight') },
// { label: 'rangeblock', value: 'rangeblock' }, // placeholder for when we add support for rangeblocks
{ label: 'spamblacklistblock', value: 'spamblacklistblock' },
{ label: 'tor', value: 'tor' },
{ label: 'webhostblock', value: 'webhostblock' },
{ label: 'zombie proxy', value: 'zombie proxy' }
]
}
];
Twinkle.block.blockGroupsPartial = [
{
label: 'Common partial block reasons',
list: [
{ label: 'Generic partial block (custom reason)', value: 'uw-pblock', selected: true },
{ label: 'Generic partial block (custom reason) - indefinite', value: 'uw-pblockindef' },
{ label: 'Edit warring', value: 'uw-ewpblock' }
]
},
{
label: 'Extended partial block reasons',
list: [
{ label: 'Arbitration enforcement', value: 'uw-aepblock' },
{ label: 'Email harassment', value: 'uw-epblock' },
{ label: 'Misusing multiple accounts', value: 'uw-acpblock' },
{ label: 'Misusing multiple accounts - indefinite', value: 'uw-acpblockindef' }
]
}
];
Twinkle.block.callback.filtered_block_groups = function twinkleblockCallbackFilteredBlockGroups(group, show_template) {
return $.map(group, function(blockGroup) {
var list = $.map(blockGroup.list, function(blockPreset) {
// only show uw-talkrevoked if reblocking
if (!Twinkle.block.currentBlockInfo && blockPreset.value === 'uw-talkrevoked') {
return;
}
var blockSettings = Twinkle.block.blockPresetsInfo[blockPreset.value];
var registrationRestrict = blockSettings.forRegisteredOnly ? Twinkle.block.isRegistered : blockSettings.forAnonOnly ? !Twinkle.block.isRegistered : true;
if (!(blockSettings.templateName && show_template) && registrationRestrict) {
var templateName = blockSettings.templateName || blockPreset.value;
return {
label: (show_template ? '{{' + templateName + '}}: ' : '') + blockPreset.label,
value: blockPreset.value,
data: [{
name: 'template-name',
value: templateName
}],
selected: !!blockPreset.selected,
disabled: !!blockPreset.disabled
};
}
});
if (list.length) {
return {
label: blockGroup.label,
list: list
};
}
});
};
Twinkle.block.callback.change_preset = function twinkleblockCallbackChangePreset(e) {
var key = e.target.form.preset.value;
if (!key) {
return;
}
e.target.form.template.value = Twinkle.block.blockPresetsInfo[key].templateName || key;
Twinkle.block.callback.update_form(e, Twinkle.block.blockPresetsInfo[key]);
Twinkle.block.callback.change_template(e);
};
Twinkle.block.callback.change_expiry = function twinkleblockCallbackChangeExpiry(e) {
var expiry = e.target.form.expiry;
if (e.target.value === 'custom') {
Morebits.quickForm.setElementVisibility(expiry.parentNode, true);
} else {
Morebits.quickForm.setElementVisibility(expiry.parentNode, false);
expiry.value = e.target.value;
}
};
Twinkle.block.seeAlsos = [];
Twinkle.block.callback.toggle_see_alsos = function twinkleblockCallbackToggleSeeAlso() {
var reason = this.form.reason.value.replace(
new RegExp('( <!--|;) ' + 'see also ' + Twinkle.block.seeAlsos.join(' and ') + '( -->)?'), ''
);
Twinkle.block.seeAlsos = Twinkle.block.seeAlsos.filter(function(el) {
return el !== this.value;
}.bind(this));
if (this.checked) {
Twinkle.block.seeAlsos.push(this.value);
}
var seeAlsoMessage = Twinkle.block.seeAlsos.join(' and ');
if (!Twinkle.block.seeAlsos.length) {
this.form.reason.value = reason;
} else if (reason.indexOf('{{') !== -1) {
this.form.reason.value = reason + ' <!-- see also ' + seeAlsoMessage + ' -->';
} else {
this.form.reason.value = reason + '; see also ' + seeAlsoMessage;
}
};
Twinkle.block.callback.update_form = function twinkleblockCallbackUpdateForm(e, data) {
var form = e.target.form, expiry = data.expiry;
// don't override original expiry if useInitialOptions is set
if (!data.useInitialOptions) {
if (Date.parse(expiry)) {
expiry = new Date(expiry).toGMTString();
form.expiry_preset.value = 'custom';
} else {
form.expiry_preset.value = data.expiry || 'custom';
}
form.expiry.value = expiry;
if (form.expiry_preset.value === 'custom') {
Morebits.quickForm.setElementVisibility(form.expiry.parentNode, true);
} else {
Morebits.quickForm.setElementVisibility(form.expiry.parentNode, false);
}
}
// boolean-flipped options, more at [[mw:API:Block]]
data.disabletalk = data.disabletalk !== undefined ? data.disabletalk : false;
data.hardblock = data.hardblock !== undefined ? data.hardblock : false;
// disable autoblock if blocking a bot
if (Twinkle.block.isRegistered && relevantUserName.search(/bot\b/i) > 0) {
data.autoblock = false;
}
$(form.field_block_options).find(':checkbox').each(function(i, el) {
// don't override original options if useInitialOptions is set
if (data.useInitialOptions && data[el.name] === undefined) {
return;
}
var check = data[el.name] === '' || !!data[el.name];
$(el).prop('checked', check);
});
if (data.prependReason && data.reason) {
form.reason.value = data.reason + '; ' + form.reason.value;
} else {
form.reason.value = data.reason || '';
}
};
Twinkle.block.callback.change_template = function twinkleblockcallbackChangeTemplate(e) {
var form = e.target.form, value = form.template.value, settings = Twinkle.block.blockPresetsInfo[value];
if (!$(form).find('[name=actiontype][value=block]').is(':checked')) {
if (settings.indefinite || settings.nonstandard) {
if (Twinkle.block.prev_template_expiry === null) {
Twinkle.block.prev_template_expiry = form.template_expiry.value || '';
}
form.template_expiry.parentNode.style.display = 'none';
form.template_expiry.value = 'indefinite';
} else if (form.template_expiry.parentNode.style.display === 'none') {
if (Twinkle.block.prev_template_expiry !== null) {
form.template_expiry.value = Twinkle.block.prev_template_expiry;
Twinkle.block.prev_template_expiry = null;
}
form.template_expiry.parentNode.style.display = 'block';
}
if (Twinkle.block.prev_template_expiry) {
form.expiry.value = Twinkle.block.prev_template_expiry;
}
Morebits.quickForm.setElementVisibility(form.notalk.parentNode, !settings.nonstandard);
Morebits.quickForm.setElementVisibility(form.noemail_template.parentNode, $(form).find('[name=actiontype][value=partial]').is(':checked') && !$(form).find('[name=actiontype][value=block]').is(':checked'));
Morebits.quickForm.setElementVisibility(form.nocreate_template.parentNode, $(form).find('[name=actiontype][value=partial]').is(':checked') && !$(form).find('[name=actiontype][value=block]').is(':checked'));
} else {
Morebits.quickForm.setElementVisibility(
form.blank_duration.parentNode,
!settings.indefinite && !settings.nonstandard
);
}
Morebits.quickForm.setElementVisibility(form.article.parentNode, !!settings.pageParam);
Morebits.quickForm.setElementVisibility(form.block_reason.parentNode, !!settings.reasonParam);
// Partial block
Morebits.quickForm.setElementVisibility(form.area.parentNode, $(form).find('[name=actiontype][value=partial]').is(':checked') && !$(form).find('[name=actiontype][value=block]').is(':checked'));
form.root.previewer.closePreview();
};
Twinkle.block.prev_template_expiry = null;
Twinkle.block.prev_block_reason = null;
Twinkle.block.prev_article = null;
Twinkle.block.prev_reason = null;
Twinkle.block.callback.preview = function twinkleblockcallbackPreview(form) {
var params = {
article: form.article.value,
blank_duration: form.blank_duration ? form.blank_duration.checked : false,
disabletalk: form.disabletalk.checked || (form.notalk ? form.notalk.checked : false),
expiry: form.template_expiry ? form.template_expiry.value : form.expiry.value,
hardblock: Twinkle.block.isRegistered ? form.autoblock.checked : form.hardblock.checked,
indefinite: (/indef|infinit|never|\*|max/).test(form.template_expiry ? form.template_expiry.value : form.expiry.value),
reason: form.block_reason.value,
template: form.template.value,
partial: $(form).find('[name=actiontype][value=partial]').is(':checked'),
pagerestrictions: $(form.pagerestrictions).val() || [],
namespacerestrictions: $(form.namespacerestrictions).val() || [],
noemail: form.noemail.checked || (form.noemail_template ? form.noemail_template.checked : false),
nocreate: form.nocreate.checked || (form.nocreate_template ? form.nocreate_template.checked : false),
area: form.area.value
};
var templateText = Twinkle.block.callback.getBlockNoticeWikitext(params);
form.previewer.beginRender(templateText, 'User_talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username
};
Twinkle.block.callback.evaluate = function twinkleblockCallbackEvaluate(e) {
var $form = $(e.target),
toBlock = $form.find('[name=actiontype][value=block]').is(':checked'),
toWarn = $form.find('[name=actiontype][value=template]').is(':checked'),
toPartial = $form.find('[name=actiontype][value=partial]').is(':checked'),
blockoptions = {}, templateoptions = {};
Twinkle.block.callback.saveFieldset($form.find('[name=field_block_options]'));
Twinkle.block.callback.saveFieldset($form.find('[name=field_template_options]'));
blockoptions = Twinkle.block.field_block_options;
templateoptions = Twinkle.block.field_template_options;
templateoptions.disabletalk = !!(templateoptions.disabletalk || blockoptions.disabletalk);
templateoptions.hardblock = !!blockoptions.hardblock;
delete blockoptions.expiry_preset; // remove extraneous
// Partial API requires this to be gone, not false or 0
if (toPartial) {
blockoptions.partial = templateoptions.partial = true;
}
templateoptions.pagerestrictions = $form.find('[name=pagerestrictions]').val() || [];
templateoptions.namespacerestrictions = $form.find('[name=namespacerestrictions]').val() || [];
// Format for API here rather than in saveFieldset
blockoptions.pagerestrictions = templateoptions.pagerestrictions.join('|');
blockoptions.namespacerestrictions = templateoptions.namespacerestrictions.join('|');
// use block settings as warn options where not supplied
templateoptions.summary = templateoptions.summary || blockoptions.reason;
templateoptions.expiry = templateoptions.template_expiry || blockoptions.expiry;
if (toBlock) {
if (blockoptions.partial) {
if (blockoptions.disabletalk && blockoptions.namespacerestrictions.indexOf('3') === -1) {
return alert('Partial blocks cannot prevent talk page access unless also restricting them from editing User talk space!');
}
if (!blockoptions.namespacerestrictions && !blockoptions.pagerestrictions) {
if (!blockoptions.noemail && !blockoptions.nocreate) { // Blank entries technically allowed [[phab:T208645]]
return alert('No pages or namespaces were selected, nor were email or account creation restrictions applied; please select at least one option to apply a partial block!');
} else if (!confirm('You are about to block with no restrictions on page or namespace editing, are you sure you want to proceed?')) {
return;
}
}
}
if (!blockoptions.expiry) {
return alert('Please provide an expiry!');
}
if (!blockoptions.reason) {
return alert('Please provide a reason for the block!');
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(e.target);
var statusElement = new Morebits.status('Executing block');
blockoptions.action = 'block';
blockoptions.user = mw.config.get('wgRelevantUserName');
// boolean-flipped options
blockoptions.anononly = blockoptions.hardblock ? undefined : true;
blockoptions.allowusertalk = blockoptions.disabletalk ? undefined : true;
/*
Check if block status changed while processing the form.
There's a lot to consider here. list=blocks provides the
current block status, but there are at least two issues with
relying on it. First, the id doesn't update on a reblock,
meaning the individual parameters need to be compared. This
can be done roughly with JSON.stringify - we can thankfully
rely on order from the server, although sorting would be
fine if not - but falsey values are problematic and is
non-ideal. More importantly, list=blocks won't indicate if a
non-blocked user is blocked then unblocked. This should be
exceedingy rare, but regardless, we thus need to check
list=logevents, which has a nicely updating logid
parameter. We can't rely just on that, though, since it
doesn't account for blocks that have expired on their own.
As such, we use both. Using some ternaries, the logid
variables are false if there's no logevents, so if they
aren't equal we defintely have a changed entry (send
confirmation). If they are equal, then either the user was
never blocked (the block statuses will be equal, no
confirmation) or there's no new block, in which case either
a block expired (different statuses, confirmation) or the
same block is still active (same status, no confirmation).
*/
api.get({
format: 'json',
action: 'query',
list: 'blocks|logevents',
letype: 'block',
lelimit: 1,
letitle: 'User:' + blockoptions.user,
bkusers: blockoptions.user
}).then(function(data) {
var block = data.query.blocks[0];
var logevents = data.query.logevents[0];
var logid = data.query.logevents.length ? logevents.logid : false;
if (logid !== Twinkle.block.blockLogId || !!block !== !!Twinkle.block.currentBlockInfo) {
var message = 'The block status of ' + mw.config.get('wgRelevantUserName') + ' has changed. ';
if (block) {
message += 'New status: ';
} else {
message += 'Last entry: ';
}
var logExpiry = '';
if (logevents.params.duration) {
if (logevents.params.duration === 'infinity') {
logExpiry = 'indefinitely';
} else {
var expiryDate = new Morebits.date(logevents.params.expiry);
logExpiry += (expiryDate.isBefore(new Date()) ? ', expired ' : ' until ') + expiryDate.calendar();
}
} else { // no duration, action=unblock, just show timestamp
logExpiry = ' ' + new Morebits.date(logevents.timestamp).calendar();
}
message += Morebits.string.toUpperCaseFirstChar(logevents.action) + 'ed by ' + logevents.user + logExpiry +
' for "' + logevents.comment + '". Do you want to override with your settings?';
if (!confirm(message)) {
Morebits.status.info('اعمال قطع دسترسی', 'توسط کاربر لغو شد'); // localized
return;
}
blockoptions.reblock = 1; // Writing over a block will fail otherwise
}
// execute block
blockoptions.token = mw.user.tokens.get('csrfToken');
var mbApi = new Morebits.wiki.api('اعمال قطع دسترسی', blockoptions, function() { // localized
statusElement.info('کامل شد'); // localized
if (toWarn) {
Twinkle.block.callback.issue_template(templateoptions);
}
});
mbApi.post();
});
} else if (toWarn) {
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(e.target);
Twinkle.block.callback.issue_template(templateoptions);
} else {
return alert('Please give Twinkle something to do!');
}
};
Twinkle.block.callback.issue_template = function twinkleblockCallbackIssueTemplate(formData) {
var userTalkPage = 'User_talk:' + mw.config.get('wgRelevantUserName');
var params = $.extend(formData, {
messageData: Twinkle.block.blockPresetsInfo[formData.template],
reason: Twinkle.block.field_template_options.block_reason,
disabletalk: Twinkle.block.field_template_options.notalk,
noemail: Twinkle.block.field_template_options.noemail_template,
nocreate: Twinkle.block.field_template_options.nocreate_template
});
Morebits.wiki.actionCompleted.redirect = userTalkPage;
Morebits.wiki.actionCompleted.notice = 'عمل انجام شد، بارگیری صفحهٔ بحث کاربر تا چند ثانیهٔ دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'User talk page modification');
wikipedia_page.setCallbackParameters(params);
wikipedia_page.setFollowRedirect(true);
wikipedia_page.load(Twinkle.block.callback.main);
};
Twinkle.block.callback.getBlockNoticeWikitext = function(params) {
var text = '{{', settings = Twinkle.block.blockPresetsInfo[params.template];
if (!settings.nonstandard) {
text += 'subst:' + params.template;
if (params.article && settings.pageParam) {
text += '|page=' + params.article;
}
if (!/te?mp|^\s*$|min/.exec(params.expiry)) {
if (params.indefinite) {
text += '|indef=yes';
} else if (!params.blank_duration) {
text += '|time=' + params.expiry;
}
}
if (!Twinkle.block.isRegistered && !params.hardblock) {
text += '|anon=yes';
}
if (params.reason) {
text += '|reason=' + params.reason;
}
if (params.disabletalk) {
text += '|notalk=yes';
}
// Currently, all partial block templates are "standard"
// Building the template, however, takes a fair bit of logic
if (params.partial) {
if (params.pagerestrictions.length || params.namespacerestrictions.length) {
var makeSentence = function (array) {
if (array.length < 3) {
return array.join(' and ');
}
var last = array.pop();
return array.join(', ') + ', and ' + last;
};
text += '|area=' + (params.indefinite ? 'certain ' : 'from certain ');
if (params.pagerestrictions.length) {
text += 'pages (' + makeSentence(params.pagerestrictions.map(function(p) {
return '[[:' + p + ']]';
}));
text += params.namespacerestrictions.length ? ') and certain ' : ')';
}
if (params.namespacerestrictions.length) {
// 1 => Talk, 2 => User, etc.
var namespaceNames = params.namespacerestrictions.map(function(id) {
return menuFormattedNamespaces[id];
});
text += '[[ویکیواژه:فضای نام]] (' + makeSentence(namespaceNames) + ')'; // localized
}
} else if (params.area) {
text += '|area=' + params.area;
} else {
if (params.noemail) {
text += '|email=yes';
}
if (params.nocreate) {
text += '|accountcreate=yes';
}
}
}
} else {
text += params.template;
}
if (settings.sig) {
text += '|sig=' + settings.sig;
}
return text + '}}';
};
Twinkle.block.callback.main = function twinkleblockcallbackMain(pageobj) {
var text = pageobj.getPageText(),
params = pageobj.getCallbackParameters(),
messageData = params.messageData,
date = new Morebits.date(pageobj.getLoadTime());
var dateHeaderRegex = date.monthHeaderRegex(), dateHeaderRegexLast, dateHeaderRegexResult;
while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) {
dateHeaderRegexResult = dateHeaderRegexLast;
}
// If dateHeaderRegexResult is null then lastHeaderIndex is never checked. If it is not null but
// \n== is not found, then the date header must be at the very start of the page. lastIndexOf
// returns -1 in this case, so lastHeaderIndex gets set to 0 as desired.
var lastHeaderIndex = text.lastIndexOf('\n==') + 1;
if (text.length > 0) {
text += '\n\n';
}
params.indefinite = (/indef|infinit|never|\*|max/).test(params.expiry);
if (Twinkle.getPref('blankTalkpageOnIndefBlock') && params.template !== 'uw-lblock' && params.indefinite) {
Morebits.status.info('اطلاعرسانی', 'صفحهٔ بحث کاربر به درخواست شما خالی شد و یک بخش با عنوان سطح ۲ و تاریخ امروز در آن افزوده شد'); // localiezd
text = date.monthHeader() + '\n';
} else if (!dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex) {
Morebits.status.info('اطلاعرسانی', 'یک بخش جدید با عنوان سطح ۲ و تاریخ امروز در صفحهٔ بحث افزوده خواهد شد، چرا که بخش مشابهی برای ماه جاری یافت نشد'); // localized
text += date.monthHeader() + '\n';
}
params.expiry = typeof params.template_expiry !== 'undefined' ? params.template_expiry : params.expiry;
text += Twinkle.block.callback.getBlockNoticeWikitext(params);
// build the edit summary
var summary = messageData.summary;
if (messageData.suppressArticleInSummary !== true && params.article) {
summary += ' بر [[:' + params.article + ']]'; // localized
}
summary += '.' + Twinkle.getPref('summaryAd');
pageobj.setPageText(text);
pageobj.setEditSummary(summary);
pageobj.setWatchlist(Twinkle.getPref('watchWarnings'));
pageobj.save();
};
})(jQuery);
// </nowiki>
j3s96yrz6gry6i7zi30ova1kgxjkhrs
کاربر:Mamouri/twinkle/friendlywelcome-2020.js
2
219488
908995
2026-07-30T16:04:23Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
908995
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** friendlywelcome.js: Welcome module
****************************************
* Mode of invocation: Tab ("Wel"), or from links on diff pages
* Active on: Any page with relevant user name (userspace,
* contribs, etc.) and diff pages
*/
Twinkle.welcome = function friendlywelcome() {
if (mw.util.getParamValue('friendlywelcome')) {
if (mw.util.getParamValue('friendlywelcome') === 'auto') {
Twinkle.welcome.auto();
} else {
Twinkle.welcome.semiauto();
}
} else {
Twinkle.welcome.normal();
}
};
Twinkle.welcome.auto = function() {
if (mw.util.getParamValue('action') !== 'edit') {
// userpage not empty, aborting auto-welcome
return;
}
Twinkle.welcome.welcomeUser();
};
Twinkle.welcome.semiauto = function() {
Twinkle.welcome.callback(mw.config.get('wgRelevantUserName'));
};
Twinkle.welcome.normal = function() {
if (mw.util.getParamValue('diff')) {
// check whether the contributors' talk pages exist yet
var $oList = $('#mw-diff-otitle2').find('span.mw-usertoollinks a.new:contains(talk)').first();
var $nList = $('#mw-diff-ntitle2').find('span.mw-usertoollinks a.new:contains(talk)').first();
if ($oList.length > 0 || $nList.length > 0) {
var spanTag = function(color, content) {
var span = document.createElement('span');
span.style.color = color;
span.appendChild(document.createTextNode(content));
return span;
};
var welcomeNode = document.createElement('strong');
var welcomeLink = document.createElement('a');
welcomeLink.appendChild(spanTag('Black', '['));
welcomeLink.appendChild(spanTag('Goldenrod', 'خوشامد')); // localized
welcomeLink.appendChild(spanTag('Black', ']'));
welcomeNode.appendChild(welcomeLink);
if ($oList.length > 0) {
var oHref = $oList.attr('href');
var oWelcomeNode = welcomeNode.cloneNode(true);
oWelcomeNode.firstChild.setAttribute('href', oHref + '&' + $.param({
'friendlywelcome': Twinkle.getPref('quickWelcomeMode') === 'auto' ? 'auto' : 'norm',
'vanarticle': Morebits.pageNameNorm
}));
$oList[0].parentNode.parentNode.appendChild(document.createTextNode(' '));
$oList[0].parentNode.parentNode.appendChild(oWelcomeNode);
}
if ($nList.length > 0) {
var nHref = $nList.attr('href');
var nWelcomeNode = welcomeNode.cloneNode(true);
nWelcomeNode.firstChild.setAttribute('href', nHref + '&' + $.param({
'friendlywelcome': Twinkle.getPref('quickWelcomeMode') === 'auto' ? 'auto' : 'norm',
'vanarticle': Morebits.pageNameNorm
}));
$nList[0].parentNode.parentNode.appendChild(document.createTextNode(' '));
$nList[0].parentNode.parentNode.appendChild(nWelcomeNode);
}
}
}
if (mw.config.get('wgRelevantUserName')) {
Twinkle.addPortletLink(function() {
Twinkle.welcome.callback(mw.config.get('wgRelevantUserName'));
}, 'خوشامد', 'friendly-welcome', 'خوشامدگویی به کاربر'); // localized
}
};
Twinkle.welcome.welcomeUser = function welcomeUser() {
Morebits.status.init(document.getElementById('mw-content-text'));
$('#catlinks').remove();
var params = {
value: Twinkle.getPref('quickWelcomeTemplate'),
article: mw.util.getParamValue('vanarticle') || '',
mode: 'auto'
};
var userTalkPage = mw.config.get('wgFormattedNamespaces')[3] + ':' + mw.config.get('wgRelevantUserName');
Morebits.wiki.actionCompleted.redirect = userTalkPage;
Morebits.wiki.actionCompleted.notice = 'خوشامدگویی انجام شد، در حال بارگزاری دوبارهٔ صفحه بحث تا چند ثانیهٔ دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'ویرایش صفحهٔ بحث کاربر'); // localized
wikipedia_page.setFollowRedirect(true);
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.welcome.callbacks.main);
};
Twinkle.welcome.callback = function friendlywelcomeCallback(uid) {
if (uid === mw.config.get('wgUserName') && !confirm('آیا از خوشامدگویی به خودتان مطمئن هستید؟...')) { // localized
return;
}
var Window = new Morebits.simpleWindow(600, 420);
Window.setTitle('خوشامدگویی به کاربر'); // localized
Window.setScriptName('توینکل'); // localiezd
// Window.addFooterLink('Welcoming Committee', 'WP:WC'); // localized [removed]
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#welcome'); // localized
var form = new Morebits.quickForm(Twinkle.welcome.callback.evaluate);
form.append({
type: 'select',
name: 'type',
label: 'نوع خوشامدگویی: ',
event: Twinkle.welcome.populateWelcomeList,
list: [
{ type: 'option', value: 'standard', label: 'خوشامدهای استاندارد', selected: !mw.util.isIPAddress(mw.config.get('wgRelevantUserName')) }, // localized
{ type: 'option', value: 'anonymous', label: 'خوشامد به کاربران ناشناس', selected: mw.util.isIPAddress(mw.config.get('wgRelevantUserName')) } // , // localized
// { type: 'option', value: 'wikiProject', label: 'WikiProject welcomes' }, // localized [removed]
// { type: 'option', value: 'nonEnglish', label: 'Non-English welcomes' } // localiezd [removed]
]
});
form.append({
type: 'div',
id: 'welcomeWorkArea',
className: 'morebits-scrollbox'
});
form.append({
type: 'input',
name: 'article',
label: '* مقالهٔ پیوند شده (در صورت پشتیبانی توسط الگو):', // localized
value: mw.util.getParamValue('vanarticle') || '',
tooltip: 'در صورت پشتیبانی توسط الگو، میتوان یک مقاله را از درون پیام خوشامد پیوند داد. برای اینکه به هیچ مقالهای پیوند داده نشود، جعبه را خالی بگذارید. الگوهایی که از پیونددهی به یک مقاله پشتیبانی میکنند با یک ستاره علامتگذاری شدهاند.' // localized
});
var previewlink = document.createElement('a');
$(previewlink).click(function() {
Twinkle.welcome.callbacks.preview(result); // |result| is defined below
});
previewlink.style.cursor = 'pointer';
previewlink.textContent = 'پیشنمایش'; // localized
form.append({ type: 'div', name: 'welcomepreview', label: [ previewlink ] });
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
// initialize the welcome list
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.type.dispatchEvent(evt);
};
Twinkle.welcome.populateWelcomeList = function(e) {
var type = e.target.value;
var container = new Morebits.quickForm.element({ type: 'fragment' });
if ((type === 'standard' || type === 'anonymous') && Twinkle.getPref('customWelcomeList').length) {
container.append({ type: 'header', label: 'الگوهای خوشامد سفارشی' }); // localized
container.append({
type: 'radio',
name: 'template',
list: Twinkle.getPref('customWelcomeList'),
event: function() {
e.target.form.article.disabled = false;
}
});
}
var sets = Twinkle.welcome.templates[type];
$.each(sets, function(label, templates) {
container.append({ type: 'header', label: label });
container.append({
type: 'radio',
name: 'template',
list: $.map(templates, function(properties, template) {
return {
value: template,
label: '{{' + template + '}}: ' + properties.description + (properties.linkedArticle ? '\u00A0*' : ''), // U+00A0 NO-BREAK SPACE
tooltip: properties.tooltip // may be undefined
};
}),
event: function(ev) {
ev.target.form.article.disabled = !templates[ev.target.value].linkedArticle;
}
});
});
var rendered = container.render();
$(e.target.form).find('div#welcomeWorkArea').empty().append(rendered);
var firstRadio = e.target.form.template[0];
firstRadio.checked = true;
var vals = sets[Object.keys(sets)[0]];
e.target.form.article.disabled = vals[firstRadio.value] ? !vals[firstRadio.value].linkedArticle : true;
};
// A list of welcome templates and their properties and syntax
// The four fields that are available are "description", "linkedArticle", "syntax", and "tooltip".
// The three magic words that can be used in the "syntax" field are:
// - $USERNAME$ - replaced by the welcomer's username, depending on user's preferences
// - $ARTICLE$ - replaced by an article name, if "linkedArticle" is true
// - $HEADER$ - adds a level 2 header (most templates already include this)
Twinkle.welcome.templates = {
'standard': {
'الگوی خوشامد': { // localized
'خوشامد': { // localized
description: 'خوشامد استاندارد', // localized
syntax: '{{subst:خوشامد}} ~~~~' // localized
}
}
},
'anonymous': {
'الگوی خوشامد': { // localized
'خوشامد': { // localized
description: 'خوشامد استاندارد', // localized
syntax: '{{subst:خوشامد}} ~~~~' // localized
}
}
}
};
Twinkle.welcome.getTemplateWikitext = function(type, template, article) {
// the iteration is required as the type=standard has two groups
var properties;
$.each(Twinkle.welcome.templates[type], function(label, templates) {
properties = templates[template];
if (properties) {
return false; // break
}
});
if (properties) {
return properties.syntax.
replace('$USERNAME$', Twinkle.getPref('insertUsername') ? mw.config.get('wgUserName') : '').
replace('$ARTICLE$', article ? article : '').
replace(/\$HEADER\$\s*/, '== Welcome ==\n\n').
replace('$EXTRA$', ''); // EXTRA is not implemented yet
}
return '{{subst:' + template + (article ? '|art=' + article : '') + '}}' +
(Twinkle.getPref('customWelcomeSignature') ? ' ~~~~' : '');
};
Twinkle.welcome.callbacks = {
preview: function(form) {
var previewDialog = new Morebits.simpleWindow(750, 400);
previewDialog.setTitle('پیشنمایش الگوی خوشامد'); // localized
previewDialog.setScriptName('خوشامد به کاربر'); // localized
previewDialog.setModality(true);
var previewdiv = document.createElement('div');
previewdiv.style.marginLeft = previewdiv.style.marginRight = '0.5em';
previewdiv.style.fontSize = 'small';
previewDialog.setContent(previewdiv);
var previewer = new Morebits.wiki.preview(previewdiv);
previewer.beginRender(Twinkle.welcome.getTemplateWikitext(form.type.value, form.getChecked('template'), form.article.value), 'User talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username
var submit = document.createElement('input');
submit.setAttribute('type', 'submit');
submit.setAttribute('value', 'Close');
previewDialog.addContent(submit);
previewDialog.display();
$(submit).click(function() {
previewDialog.close();
});
},
main: function(pageobj) {
var params = pageobj.getCallbackParameters();
var text = pageobj.getPageText();
// abort if mode is auto and form is not empty
if (pageobj.exists() && params.mode === 'auto') {
Morebits.status.info('Warning', 'صفحهٔ بحث کاربر خالی نیست؛ در حال چشمپوشی از خوشامدگویی خودکار'); // localized
Morebits.wiki.actionCompleted.event();
return;
}
var welcomeText = Twinkle.welcome.getTemplateWikitext(params.type, params.value, params.article);
if (Twinkle.getPref('topWelcomes')) {
text = welcomeText + '\n\n' + text;
} else {
text += '\n' + welcomeText;
}
var summaryText = 'به ویکیواژه خوشآمدید!'; // localized
pageobj.setPageText(text);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchWelcomes'));
pageobj.setCreateOption('recreate');
pageobj.save();
}
};
Twinkle.welcome.callback.evaluate = function friendlywelcomeCallbackEvaluate(e) {
var form = e.target;
var params = {
type: form.type.value,
value: form.getChecked('template'),
article: form.article.value,
mode: 'manual'
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
var userTalkPage = mw.config.get('wgFormattedNamespaces')[3] + ':' + mw.config.get('wgRelevantUserName');
Morebits.wiki.actionCompleted.redirect = userTalkPage;
Morebits.wiki.actionCompleted.notice = 'خوشامدگویی انجام شد، در حال بارگزاری دوبارهٔ صفحهٔ بحث تا چند لحظه دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'User talk page modification');
wikipedia_page.setFollowRedirect(true);
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.welcome.callbacks.main);
};
})(jQuery);
// </nowiki>
ccm7zsk1kxt42blgeguidv5ff8kou6x
کاربر:Mamouri/twinkle/friendlyshared-2020.js
2
219489
908996
2026-07-30T16:04:30Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
908996
javascript
text/javascript
// <nowiki>
(function($) { // eslint-disable-line no-unused-vars
/*
****************************************
*** friendlyshared.js: Shared IP tagging module
****************************************
* Mode of invocation: Tab ("Shared")
* Active on: Existing IP user talk pages
*/
Twinkle.shared = function friendlyshared() {
if (mw.config.get('wgNamespaceNumber') === 3 && mw.util.isIPAddress(mw.config.get('wgTitle'))) {
var username = mw.config.get('wgRelevantUserName');
Twinkle.addPortletLink(function() {
Twinkle.shared.callback(username);
}, 'آیپی اشتراکی', 'friendly-shared', 'برچسبزدن آیپیهای اشتراکی'); // localized
}
};
Twinkle.shared.callback = function friendlysharedCallback() {
var Window = new Morebits.simpleWindow(600, 420);
Window.setTitle('برچسب زدن نشانیهای آیپی اشتراکی'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#shared'); // localiezd
var form = new Morebits.quickForm(Twinkle.shared.callback.evaluate);
var div = form.append({
type: 'div',
id: 'sharedip-templatelist',
className: 'morebits-scrollbox'
}
);
div.append({ type: 'header', label: 'الگوهای نشانی آیپی اشتراکی' }); // localized
div.append({ type: 'radio', name: 'shared', list: Twinkle.shared.standardList,
event: function(e) {
Twinkle.shared.callback.change_shared(e);
e.stopPropagation();
}
});
var org = form.append({ type: 'field', label: 'سایر جزئیات (اختیاری) را پر کنید و روی دکمهٔ پایین فرم کلیک کنید' }); // localized
org.append({
type: 'input',
name: 'organization',
label: 'مالک/گردانندهٔ نشانی آیپی', // localized
disabled: true,
tooltip: 'میتوانید به صورت اختیاری نام سازمانی را که مالک/گردانندهٔ نشانی آیپی است، وارد کنید. در صورت لزوم میتوانید از ویکیکد استفاده کنید.' // localized
}
);
org.append({
type: 'input',
name: 'host',
label: 'نام میزبان (اختیاری)', // localized
disabled: true,
tooltip: 'نام میزبان (نظیر proxy.example.com) میتواند به صورت اختیاری در اینجا وارد شود و با الگو پیوند خواهد یافت.' // localized
}
);
org.append({
type: 'input',
name: 'contact',
label: 'اطلاعات تماس (فقط در صورت درخواست)', // localized
disabled: true,
tooltip: 'میتوانید به طور اختیاری برخی از اطلاعات تماس را برای آن سازمان وارد کنید. از این پارامتر فقط در صورتی استفاده کنید که سازمان بهطور خاص درخواست کرده است که آن را اضافه کنید. در صورت لزوم میتوانید از ویکیکد استفاده کنید.' // localized
}
);
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
};
Twinkle.shared.standardList = [
{
label: '{{آیپی مشترک}}: الگوی استاندارد نشانی آیپی مشترک', // localized
value: 'آیپی مشترک', // localized
tooltip: 'الگوی صفحه بحث کاربر آیپی که اطلاعات مفیدی را برای کاربران آیپی و کسانی که مایل به هشدار به آنها یا بستن یا تحریم آنها هستند نشان میدهد' // localized
},
{
label: '{{آیپی مشترک آموزشی}}: الگوی اصلاحشده نشانی آیپی مشترک برای مؤسسات آموزشی', // localized
value: 'آیپی مشترک آموزشی' // localized
},
{
label: '{{آیپی مشترک شرکتی}}: الگوی اصلاحشده نشانی آیپی مشترک برای مشاغل', // localized
value: 'آیپی مشترک آموزشی' // localized
},
{
label: '{{نشانی آیپی مشترک (عمومی)}}: الگوی اصلاحشده نشانی آیپی مشترک برای پایانههای عمومی', // localized
value: 'آیپی مشترک (عمومی)' // localized
},
{
label: '{{آیپی مشترک دولتی}}: الگوی اصلاحشده نشانی آیپی مشترک برای آژانسهای دولتی یا دفترهای خدماتی', // localized
value: 'آیپی مشترک دولتی' // localized
},
{
label: '{{آیپی پویا}}: الگوی اصلاحشده نشانی آیپی مشترک برای سازمانهایی با آدرسدهی پویا', // localized
value: 'آیپی پویا' // localized
},
{
label: '{{آیپی ایستا}}: الگوی اصلاحشده نشانی آیپی مشترک برای آدرسهای آیپی ثابت', // localized
value: 'آیپی ایستا' // localized
},
{
label: '{{آیاسپی}}: الگوی اصلاحشده نشانی آیپی مشترک برای سازمانهای ISP (به طور خاص پراکسی)', // localized
value: 'آیاسپی' // localized
},
{
label: '{{آیپی تلفن همراه}}:الگوی اصلاحشده نشانی آیپی مشترک برای شرکتهای تلفن همراه و مشتریان آنها', // localized
value: 'آیپی تلفن همراه' // localized
},
{
label: '{{Whois}}: الگوی آدرسهای آیپی که نیاز به نظارت دارند، اما معلوم نیست که ایستا، پویا یا مشترک هستند', // localized
value: 'Whois'
}
];
Twinkle.shared.callback.change_shared = function friendlysharedCallbackChangeShared(e) {
e.target.form.contact.disabled = e.target.value !== 'آیپی مشترک آموزشی'; // only supported by {{Shared IP edu}} // localized
e.target.form.organization.disabled = false;
e.target.form.host.disabled = e.target.value === 'Whois'; // host= not supported by {{Whois}}
};
Twinkle.shared.callbacks = {
main: function(pageobj) {
var params = pageobj.getCallbackParameters();
var pageText = pageobj.getPageText();
var found = false;
var text = '{{';
for (var i = 0; i < Twinkle.shared.standardList.length; i++) {
var tagRe = new RegExp('(\\{\\{' + Twinkle.shared.standardList[i].value + '(\\||\\}\\}))', 'im');
if (tagRe.exec(pageText)) {
Morebits.status.warn('Info', 'الگوی {{' + Twinkle.shared.standardList[i].value + '}} روی صفحهٔ بحث پیدا شد؛ نصب الگو متوقف شد.'); // localized
found = true;
}
}
if (found) {
return;
}
Morebits.status.info('Info', 'الگوی نشانی آیپی مشترک را در بالای صفحه بحث کاربر اضافه میکند.'); // localized
text += params.value + '|' + params.organization;
if (params.value === 'Shared IP edu' && params.contact !== '') {
text += '|' + params.contact;
}
if (params.value !== 'Whois' && params.host !== '') {
text += '|host=' + params.host;
}
text += '}}\n\n';
var summaryText = 'Added {{[[Template:' + params.value + '|' + params.value + ']]}} template.';
pageobj.setPageText(text + pageText);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setMinorEdit(Twinkle.getPref('markSharedIPAsMinor'));
pageobj.setCreateOption('recreate');
pageobj.save();
}
};
Twinkle.shared.callback.evaluate = function friendlysharedCallbackEvaluate(e) {
var shared = e.target.getChecked('shared');
if (!shared || shared.length <= 0) {
alert('برای استفاده باید یک الگوی نشانی آیپی مشترک انتخاب کنید!'); // localized
return;
}
var value = shared[0];
if (e.target.organization.value === '') {
alert('برای الگوی {{' + value + '}} باید نام سازمان را وارد کنید!'); // localized
return;
}
var params = {
value: value,
organization: e.target.organization.value,
host: e.target.host.value,
contact: e.target.contact.value
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(e.target);
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'برچسب زدن کامل شد، بارگیری صفحه بحث ظرف چند ثانیهٔ دیگر'; // localized
var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'اصلاح صفحهٔ بحث کاربر'); // localized
wikipedia_page.setFollowRedirect(true);
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.shared.callbacks.main);
};
})(jQuery);
// </nowiki>
fesu7o1ud86hzjlty32y73c9duy6k9u
کاربر:Mamouri/twinkle/friendlytalkback-2020.js
2
219490
908997
2026-07-30T16:04:36Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
908997
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** friendlytalkback.js: Talkback module
****************************************
* Mode of invocation: Tab ("TB")
* Active on: Any page with relevant user name (userspace, contribs, etc.)
* Config directives in: FriendlyConfig
*/
Twinkle.talkback = function() {
if (!mw.config.get('wgRelevantUserName')) {
return;
}
Twinkle.addPortletLink(Twinkle.talkback.callback, 'بازبحث', 'friendly-talkback', 'گفتگوی آسان'); // localized
};
Twinkle.talkback.callback = function() {
if (mw.config.get('wgRelevantUserName') === mw.config.get('wgUserName') && !confirm("اوضاع این قدر بد است که میخواهید با خودتان گفتگو کنید؟")) { // localized
return;
}
var Window = new Morebits.simpleWindow(600, 350);
Window.setTitle('بازبحث'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('دربارهٔ {{بازبحث}}', 'الگو:بازبحث'); //localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#talkback'); //localized
var form = new Morebits.quickForm(Twinkle.talkback.evaluate);
form.append({ type: 'radio', name: 'tbtarget',
list: [
{
label: 'بازبحث: صفحهٔ بحث من', //localized
value: 'mytalk',
checked: 'true'
},
{
label: 'بازبحث: صفحهٔ بحث کاربر دیگر', //localized
value: 'usertalk'
},
{
label: 'بازبحث: صفحهای دیگر', //localized
value: 'other'
},
{
label: '«لطفاً ببینید»', //localized
value: 'see'
},
{
label: 'آگاهسازی درخصوص تابلوی اعلانات', //localized
value: 'notice'
},
{
label: '«رایانامه دارید»', //localized
value: 'mail'
}
],
event: Twinkle.talkback.changeTarget
});
form.append({
type: 'field',
label: 'Work area',
name: 'work_area'
});
var previewlink = document.createElement('a');
$(previewlink).click(function() {
Twinkle.talkback.preview(result); // |result| is defined below
});
previewlink.style.cursor = 'pointer';
previewlink.textContent = 'پیشنمایش'; // localized
form.append({ type: 'div', id: 'talkbackpreview', label: [ previewlink ] });
form.append({ type: 'div', id: 'friendlytalkback-previewbox', style: 'display: none' });
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
result.previewer = new Morebits.wiki.preview($(result).find('div#friendlytalkback-previewbox').last()[0]);
// We must init the
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.tbtarget[0].dispatchEvent(evt);
// Check whether the user has opted out from talkback
var query = {
action: 'query',
prop: 'extlinks',
titles: 'بحث کاربر:' + mw.config.get('wgRelevantUserName'), //localized
elquery: 'userjs.invalid/noTalkback',
ellimit: '1'
};
var wpapi = new Morebits.wiki.api('دریافت وضعیت استثنا از بازبحث', query, Twinkle.talkback.callback.optoutStatus); // localized
wpapi.post();
};
Twinkle.talkback.optout = '';
Twinkle.talkback.callback.optoutStatus = function(apiobj) {
var $el = $(apiobj.getXML()).find('el');
if ($el.length) {
Twinkle.talkback.optout = mw.config.get('wgRelevantUserName') + ' ترجیح میدهد بازبحث دریافت نکند'; // localized
var url = $el.text();
var reason = mw.util.getParamValue('reason', url);
Twinkle.talkback.optout += reason ? ': ' + reason : '.';
}
$('#twinkle-talkback-optout-message').text(Twinkle.talkback.optout);
};
var prev_page = '';
var prev_section = '';
var prev_message = '';
Twinkle.talkback.changeTarget = function(e) {
var value = e.target.values;
var root = e.target.form;
var old_area = Morebits.quickForm.getElements(root, 'work_area')[0];
if (root.section) {
prev_section = root.section.value;
}
if (root.message) {
prev_message = root.message.value;
}
if (root.page) {
prev_page = root.page.value;
}
var work_area = new Morebits.quickForm.element({
type: 'field',
label: 'اطلاعات بازبحث', // localized
name: 'work_area'
});
root.previewer.closePreview();
switch (value) {
case 'mytalk':
/* falls through */
default:
work_area.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-talkback-optout-message'
});
work_area.append({
type: 'input',
name: 'section',
label: 'عنوان بخش (اختیاری)', // localized
tooltip: 'عنوان بخشی از صفحهٔ بحث شما که در آن برای کاربر پیام گذاشتهاید. اگر چنین بخشی وجود ندارد این جعبه را خالی بگذارید.', // localized
value: prev_section
});
break;
case 'usertalk':
work_area.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-talkback-optout-message'
});
work_area.append({
type: 'input',
name: 'page',
label: 'کاربر (اجباری)', // localized
tooltip: 'نام کاربری که در صفحهٔ بحثش پیام گذاشتهاید. این پارامتر اجباری است.', // localized
value: prev_page,
required: true
});
work_area.append({
type: 'input',
name: 'section',
label: 'عنوان بخش (اختیاری)', // localized
tooltip: 'عنوان بخشی از صفحه که در آن پیام گذاشتهاید. اگر چنین بخشی وجود ندارد این پارامتر را خالی بگذارید.', // localized
value: prev_section
});
break;
case 'notice':
var noticeboard = work_area.append({
type: 'select',
name: 'noticeboard',
label: 'تابلوی اعلانات:', // localized
event: function(e) {
if (e.target.value === 'afchd') {
Morebits.quickForm.overrideElementLabel(e.target.form.section, 'عنوان پیشنویس (بدون پیشوند «پیشنویس»): '); // localized
Morebits.quickForm.setElementTooltipVisibility(e.target.form.section, false);
} else {
Morebits.quickForm.resetElementLabel(e.target.form.section);
Morebits.quickForm.setElementTooltipVisibility(e.target.form.section, true);
}
}
});
$.each(Twinkle.talkback.noticeboards, function(value, data) {
noticeboard.append({
type: 'option',
label: data.label,
value: value,
selected: !!data.defaultSelected
});
});
work_area.append({
type: 'input',
name: 'section',
label: 'ریسهٔ پیوندشده', // localized
tooltip: 'عنوان ریسهٔ مرتبط در تابلوی اعلانات.', // localized
value: prev_section
});
break;
case 'other':
work_area.append({
type: 'div',
label: '',
style: 'color: red',
id: 'twinkle-talkback-optout-message'
});
work_area.append({
type: 'input',
name: 'page',
label: 'نام کامل صفحه (اجباری)', // localized
tooltip: "نام کامل صفحهای که در آن برای کاربر پیام گذاشتهاید. نام را با پیشوند وارد کنید، مثلاً «ویکیواژه:قهوهخانه/فنی». این پارامتر اجباری است.", // localized
value: prev_page,
required: true
});
work_area.append({
type: 'input',
name: 'section',
label: 'عنوان بخش (اختیاری)', // localized
tooltip: 'عنوان بخشی از صفحه که در آن پیام گذاشتهاید. اگر چنین بخشی وجود ندارد این پارامتر را خالی بگذارید.', // localized
value: prev_section
});
break;
case 'mail':
work_area.append({
type: 'input',
name: 'section',
label: 'موضوع رایانامه (اختیاری)', // localized
tooltip: 'عبارتی که به عنوان موضوع ایمیل ذکر کردید.' // localized
});
break;
case 'see':
work_area.append({
type: 'input',
name: 'page',
label: 'نام کامل صفحه (اجباری)', // localized
tooltip: "نام کامل صفحهای که در آن برای کاربر پیام گذاشتهاید. نام را با پیشوند وارد کنید، مثلاً «ویکیواژه:قهوهخانه/فنی». این پارامتر اجباری است.", // localized
value: prev_page,
required: true
});
work_area.append({
type: 'input',
name: 'section',
label: 'عنوان بخش (اختیاری)', // localized
tooltip: 'عنوان بخشی از صفحه که در آن پیام گذاشتهاید. اگر چنین بخشی وجود ندارد این پارامتر را خالی بگذارید.', // localized
value: prev_section
});
break;
}
if (value !== 'notice') {
work_area.append({ type: 'textarea', label: 'پیام اضافه (اختیاری):', name: 'message', tooltip: 'پیام اضافهای که میخواهید زیر الگوی بازبحث نوشته شود. امضای شما به طور خودکار به انتهای پیام اضافه خواهد شد.' }); // localized
}
work_area = work_area.render();
root.replaceChild(work_area, old_area);
if (root.message) {
root.message.value = prev_message;
}
$('#twinkle-talkback-optout-message').text(Twinkle.talkback.optout);
};
Twinkle.talkback.noticeboards = {
'an': {
label: "وو:تام (تابلوی اعلانات مدیران)", // localized
text: '== ' + Twinkle.getPref('adminNoticeHeading') + ' ==\n' +
"{{subst:آگاهسازی تام|بخش=$SECTION}} ~~~~", // localized
editSummary: 'آگاهسازی از بحثی که در [[ویکیواژه:تابلوی اعلانات مدیران]] مطرح شدهاست' // localized
},
'an3': {
label: "وو:ن۳ب (تابلوی اعلانات جنگ ویرایشی)", // localized
text: '{{subst:آگاهسازی تام۳|$SECTION}} ~~~~', // localized
editSummary: "آگاهسازی از شکایتی که در [[ویکیواژه:تابلوی اعلانات مدیران/نقض ۳ برگردان]] مطرح شدهاست." // localized
},
'ani': {
label: "شکایت در وو:تام (تابلوی اعلانات مدیران)", // localized
text: '== ' + Twinkle.getPref('adminNoticeHeading') + ' ==\n' +
"{{subst:آگاهسازی شکایت در تام|بخش=$SECTION}} ~~~~", // localized
editSummary: 'آگاهسازی از شکایتی که در [[ویکیواژه:تابلوی اعلانات مدیران]] مطرح شدهاست', // localized
defaultSelected: true
},
// let's keep AN and its cousins at the top
/*
'afchd': {
label: 'WP:AFCHD (Articles for creation/Help desk)',
text: '{{subst:AFCHD/u|$SECTION}} ~~~~',
editSummary: 'You have replies at the [[Wikipedia:AFCHD|Articles for Creation Help Desk]]'
},
'coin': {
label: 'WP:COIN (Conflict of interest noticeboard)',
text: '{{subst:Coin-notice|thread=$SECTION}} ~~~~',
editSummary: 'Notice of discussion at [[Wikipedia:Conflict of interest noticeboard]]'
},
*/ // localized [removed]
'drn': {
label: 'وو:تاحا (تابلوی اعلانات حل اختلاف)', // localized
text: '{{subst:آگاهسازی تاحا|بخش=$SECTION}} ~~~~', // localized
editSummary: 'آگاهسازی از بحثی در [[ویکیواژه:تابلوی اعلانات حل اختلاف]]' // localized
}/*,
'effp': {
label: 'WP:EFFP/R (Edit filter false positive report)',
text: '{{EFFPReply|1=$SECTION|2=~~~~}}',
editSummary: 'You have replies to your [[Wikipedia:Edit filter/False positives/Reports|edit filter false positive report]]'
},
'hd': {
label: 'WP:HD (Help desk)',
text: '== Your question at the Help desk ==\n' + '{{helpdeskreply|1=$SECTION|ts=~~~~~}}',
editSummary: 'You have replies at the [[Wikipedia:Help desk|Wikipedia help desk]]'
},
'th': {
label: 'WP:THQ (Teahouse question forum)',
text: "== Teahouse talkback: you've got messages! ==\n{{WP:Teahouse/Teahouse talkback|WP:Teahouse/Questions|$SECTION|ts=~~~~}}",
editSummary: 'You have replies at the [[Wikipedia:Teahouse/Questions|Teahouse question board]]'
},
'otrs': {
label: 'WP:OTRS/N (OTRS noticeboard)',
text: '{{OTRSreply|1=$SECTION|2=~~~~}}',
editSummary: 'You have replies at the [[Wikipedia:OTRS noticeboard|OTRS noticeboard]]'
}
*/ // localized [removed]
};
Twinkle.talkback.evaluate = function(e) {
var form = e.target;
var tbtarget = form.getChecked('tbtarget')[0];
var page, message;
var section = form.section.value;
var editSummary;
if (tbtarget === 'notice') {
page = form.noticeboard.value;
editSummary = Twinkle.talkback.noticeboards[page].editSummary;
} else {
// usertalk, other, see
page = form.page ? form.page.value : mw.config.get('wgUserName');
if (form.message) {
message = form.message.value.trim();
}
if (tbtarget === 'mail') {
editSummary = "اعلان: شما رایانامه دریافت کردید"; // localized
} else if (tbtarget === 'see') {
editSummary = 'لطفا بحث در [[:' + page + (section ? '#' + section : '') + ']] را ببینید'; // localized
} else { // tbtarget one of mytalk, usertalk, other
editSummary = 'بازبحث ([[:'; // localized
if (tbtarget !== 'other' && !/^\s*user talk:/i.test(page)) {
editSummary += 'بحث کاربر:'; // localized
}
editSummary += page + (section ? '#' + section : '') + ']])';
}
}
var text = '\n\n' + Twinkle.talkback.getNoticeWikitext(tbtarget, page, section, message);
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
var fullUserTalkPageName = mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').user_talk] + ':' + mw.config.get('wgRelevantUserName');
Morebits.wiki.actionCompleted.redirect = fullUserTalkPageName;
Morebits.wiki.actionCompleted.notice = 'بازبحث کامل شد؛ بارگیری مجدد صفحه بحث در چند ثانیه'; // localized
var talkpage = new Morebits.wiki.page(fullUserTalkPageName, 'اضافه کردن بازبحث'); // localized
talkpage.setAppendText(text);
talkpage.setEditSummary(editSummary + Twinkle.getPref('summaryAd'));
talkpage.setCreateOption('recreate');
talkpage.setMinorEdit(Twinkle.getPref('markTalkbackAsMinor'));
talkpage.setFollowRedirect(true);
talkpage.append();
};
Twinkle.talkback.preview = function(form) {
var tbtarget = form.getChecked('tbtarget')[0];
var section = form.section.value;
var page, message;
if (tbtarget === 'notice') {
page = form.noticeboard.value;
} else {
// usertalk, other, see
page = form.page ? form.page.value : mw.config.get('wgUserName');
if (form.message) {
message = form.message.value.trim();
}
}
var noticetext = Twinkle.talkback.getNoticeWikitext(tbtarget, page, section, message);
form.previewer.beginRender(noticetext, 'User_talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username
};
Twinkle.talkback.getNoticeWikitext = function(tbtarget, page, section, message) {
var text;
if (tbtarget === 'notice') {
text = Morebits.string.safeReplace(Twinkle.talkback.noticeboards[page].text, '$SECTION', section);
} else if (tbtarget === 'see') {
text = '{{جا:دعوت|مکان=' + page + (section ? '#' + section : '') + '|بیشتر=' + message.trim() + '}}'; // lacalized
} else {
text = '==';
if (tbtarget === 'mail') {
text += Twinkle.getPref('mailHeading') + '==\n' + "{{رایانامه|subject=" + section; // localized
} else { // tbtarget one of mytalk, usertalk, other
// clean talkback heading: strip section header markers that were erroneously suggested in the documentation
text += Twinkle.getPref('talkbackHeading').replace(/^\s*=+\s*(.*?)\s*=+$\s*/, '$1') +
'==\n' + '{{بازبحث|' + page + (section ? '|' + section : ''); // localized
}
text += '|ts=~~~~~}}';
if (message) {
text += '\n' + message + ' ~~~~';
} else if (Twinkle.getPref('insertTalkbackSignature')) {
text += '\n~~~~';
}
}
return text;
};
})(jQuery);
// </nowiki>
e02mt0v66id0nnj59dk3ogh6u9oikgy
کاربر:Mamouri/twinkle/twinklespeedy-2020.js
2
219491
908998
2026-07-30T16:04:43Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
908998
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** twinklespeedy.js: CSD module
****************************************
* Mode of invocation: Tab ("CSD")
* Active on: Non-special, existing pages
*
* NOTE FOR DEVELOPERS:
* If adding a new criterion, add it to the appropriate places at the top of
* twinkleconfig.js. Also check out the default values of the CSD preferences
* in twinkle.js, and add your new criterion to those if you think it would be
* good.
*/
Twinkle.speedy = function twinklespeedy() {
// Disable on:
// * special pages
// * non-existent pages
if (mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId')) {
return;
}
Twinkle.addPortletLink(Twinkle.speedy.callback, 'حذف سریع', 'tw-csd', Morebits.userIsSysop ? 'حذف صفحه طبق وو:محس' : 'درخواست حذف صفحه طبق وو:محس'); // localized
};
// This function is run when the CSD tab/header link is clicked
Twinkle.speedy.callback = function twinklespeedyCallback() {
Twinkle.speedy.initDialog(Morebits.userIsSysop ? Twinkle.speedy.callback.evaluateSysop : Twinkle.speedy.callback.evaluateUser, true);
};
// Used by unlink feature
Twinkle.speedy.dialog = null;
// Used throughout
Twinkle.speedy.hasCSD = !!$('#delete-reason').length;
// The speedy criteria list can be in one of several modes
Twinkle.speedy.mode = {
sysopSingleSubmit: 1, // radio buttons, no subgroups, submit when "Submit" button is clicked
sysopRadioClick: 2, // radio buttons, no subgroups, submit when a radio button is clicked
sysopMultipleSubmit: 3, // check boxes, subgroups, "Submit" button already present
sysopMultipleRadioClick: 4, // check boxes, subgroups, need to add a "Submit" button
userMultipleSubmit: 5, // check boxes, subgroups, "Submit" button already pressent
userMultipleRadioClick: 6, // check boxes, subgroups, need to add a "Submit" button
userSingleSubmit: 7, // radio buttons, subgroups, submit when "Submit" button is clicked
userSingleRadioClick: 8, // radio buttons, subgroups, submit when a radio button is clicked
// are we in "delete page" mode?
// (sysops can access both "delete page" [sysop] and "tag page only" [user] modes)
isSysop: function twinklespeedyModeIsSysop(mode) {
return mode === Twinkle.speedy.mode.sysopSingleSubmit ||
mode === Twinkle.speedy.mode.sysopMultipleSubmit ||
mode === Twinkle.speedy.mode.sysopRadioClick ||
mode === Twinkle.speedy.mode.sysopMultipleRadioClick;
},
// do we have a "Submit" button once the form is created?
hasSubmitButton: function twinklespeedyModeHasSubmitButton(mode) {
return mode === Twinkle.speedy.mode.sysopSingleSubmit ||
mode === Twinkle.speedy.mode.sysopMultipleSubmit ||
mode === Twinkle.speedy.mode.sysopMultipleRadioClick ||
mode === Twinkle.speedy.mode.userMultipleSubmit ||
mode === Twinkle.speedy.mode.userMultipleRadioClick ||
mode === Twinkle.speedy.mode.userSingleSubmit;
},
// is db-multiple the outcome here?
isMultiple: function twinklespeedyModeIsMultiple(mode) {
return mode === Twinkle.speedy.mode.userMultipleSubmit ||
mode === Twinkle.speedy.mode.sysopMultipleSubmit ||
mode === Twinkle.speedy.mode.userMultipleRadioClick ||
mode === Twinkle.speedy.mode.sysopMultipleRadioClick;
}
};
// Prepares the speedy deletion dialog and displays it
Twinkle.speedy.initDialog = function twinklespeedyInitDialog(callbackfunc) {
var dialog;
Twinkle.speedy.dialog = new Morebits.simpleWindow(Twinkle.getPref('speedyWindowWidth'), Twinkle.getPref('speedyWindowHeight'));
dialog = Twinkle.speedy.dialog;
dialog.setTitle('انتخاب معیار حذف سریع'); // localized
dialog.setScriptName('توینکل'); // localized
dialog.addFooterLink('سیاست حذف سریع', 'وو:محس'); // localized
dialog.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#speedy'); // localized
var form = new Morebits.quickForm(callbackfunc, Twinkle.getPref('speedySelectionStyle') === 'radioClick' ? 'change' : null);
if (Morebits.userIsSysop) {
form.append({
type: 'checkbox',
list: [
{
label: 'فقط برچسب بزن، حذف نکن', // localized
value: 'tag_only',
name: 'tag_only',
tooltip: 'اگر فقط قصد دارید به صفحه برچسب بزنید، نه این که آن را حذف سریع کنید، این گزینه را انتخاب کنید.', // localized
checked: !(Twinkle.speedy.hasCSD || Twinkle.getPref('deleteSysopDefaultToDelete')),
event: function(event) {
var cForm = event.target.form;
var cChecked = event.target.checked;
// enable talk page checkbox
if (cForm.talkpage) {
cForm.talkpage.checked = !cChecked && Twinkle.getPref('deleteTalkPageOnDelete');
}
// enable redirects checkbox
cForm.redirects.checked = !cChecked;
// enable delete multiple
cForm.delmultiple.checked = false;
// enable notify checkbox
cForm.notify.checked = cChecked;
// enable deletion notification checkbox
cForm.warnusertalk.checked = !cChecked && !Twinkle.speedy.hasCSD;
// enable multiple
cForm.multiple.checked = false;
// enable requesting creation protection
cForm.salting.checked = false;
Twinkle.speedy.callback.modeChanged(cForm);
event.stopPropagation();
}
}
]
});
var deleteOptions = form.append({
type: 'div',
name: 'delete_options'
});
deleteOptions.append({
type: 'header',
label: 'گزینههای حذف' // localized
});
if (mw.config.get('wgNamespaceNumber') % 2 === 0 && (mw.config.get('wgNamespaceNumber') !== 2 || (/\//).test(mw.config.get('wgTitle')))) { // hide option for user pages, to avoid accidentally deleting user talk page
deleteOptions.append({
type: 'checkbox',
list: [
{
label: 'صفحه بحث را نیز حذف کن', // localized
value: 'talkpage',
name: 'talkpage',
tooltip: "این گزینه صفحهٔ بحث را هم حذف میکند. اگر معیار پ۸ (پروندهٔ موجود در انبار) را انتخاب کنید، این گزینه نادیده گرفته میشود و صفحهٔ بحث حذف نمیشود.", // localized
checked: Twinkle.getPref('deleteTalkPageOnDelete'),
event: function(event) {
event.stopPropagation();
}
}
]
});
}
deleteOptions.append({
type: 'checkbox',
list: [
{
label: 'همچنین حذف تغییرمسیرها', // localized
value: 'redirects',
name: 'redirects',
tooltip: 'این گزینه تمام تغییرمسیرهای ورودی به صفحه را نیز حذف میکند. از این گزینه برای حذفهایی که جهت انتقال یا ادغام صفحه هستند استفاده نکنید.', // localized
checked: Twinkle.getPref('deleteRedirectsOnDelete'),
event: function(event) {
event.stopPropagation();
}
}
]
});
deleteOptions.append({
type: 'checkbox',
list: [
{
label: 'حذف بر اساس چند معیار', // localized
value: 'delmultiple',
name: 'delmultiple',
tooltip: 'اگر این گزینه را انتخاب کنید، میتوانید بیش از یک معیار را به عنوان دلیل حذف صفحه برگزینید. برای مثال، ترکیب معیار ع۱۱ و م۷ برای حذف مقالهها رایج است.', // localized
event: function(event) {
Twinkle.speedy.callback.modeChanged(event.target.form);
event.stopPropagation();
}
}
]
});
deleteOptions.append({
type: 'checkbox',
list: [
{
label: 'آگاه کردن سازندهٔ صفحه از حذف آن', // localized
value: 'warnusertalk',
name: 'warnusertalk',
tooltip: 'یک الگوی آگاهسازی روی صفحهٔ بحث کاربری که صفحه را ایجاد کرده قرار داده میشود، به شرط آن که در ترجیحات توینکل، آگاهسازی را برای این معیار فعال کرده باشید ' + // localized
'و این گزینه را هم تیک زده باشید. ممکن است برای سازندهٔ مقاله پیام خوشامدی هم فرستاده شود.', // localized
checked: !Twinkle.speedy.hasCSD,
event: function(event) {
event.stopPropagation();
}
}
]
});
}
var tagOptions = form.append({
type: 'div',
name: 'tag_options'
});
if (Morebits.userIsSysop) {
tagOptions.append({
type: 'header',
label: 'گزینههای مربوط به برچسبها' // localized
});
}
tagOptions.append({
type: 'checkbox',
list: [
{
label: 'آگاه کردن سازندهٔ صفحه در صورت امکان', // localized
value: 'notify',
name: 'notify',
tooltip: 'یک الگوی آگاهسازی روی صفحهٔ بحث کاربری که صفحه را ایجاد کرده قرار میگیرد، به شرط آن که در ترجیحات توینکل، آگاهسازی را برای این معیار فعال کرده باشید ' + // localized
'و این گزینه را هم تیک بزنید. ممکن است برای سازندهٔ صفحه پیام خوشامدی هم فرستاده شود.', // localized
checked: !Morebits.userIsSysop || !(Twinkle.speedy.hasCSD || Twinkle.getPref('deleteSysopDefaultToDelete')),
event: function(event) {
event.stopPropagation();
}
}
]
});
tagOptions.append({
type: 'checkbox',
list: [
{
label: 'برچسب برای محافظت در برابر ایجاد', // localized
value: 'salting',
name: 'salting',
tooltip: 'اگر این گزینه را انتخاب کنید، کنار برچسب حذف سریع یک برچسب {{نمک}} هم قرار میدهد تا از مدیران درخواست کند که صفحه پس از حذف، در برابر ایجاد هم محافظت شود. فقط در صورتی این گزینه را استفاده کنید که صفحه بارها ایجاد شده باشد.', // localized
event: function(event) {
event.stopPropagation();
}
}
]
});
tagOptions.append({
type: 'checkbox',
list: [
{
label: 'برچسب با چند دلیل', // localized
value: 'multiple',
name: 'multiple',
tooltip: 'اگر این گزینه را انتخاب کنید، میتوانید بیش از یک معیار را به عنوان دلیل حذف صفحه برگزینید. برای مثال، ترکیب معیار ع۱۱ و م۷ برای حذف مقالهها رایج است.', // localized
event: function(event) {
Twinkle.speedy.callback.modeChanged(event.target.form);
event.stopPropagation();
}
}
]
});
form.append({
type: 'div',
name: 'work_area',
label: 'فعال کردن پودمان حذف سریع ناموفق بود. لطفاً دوباره تلاش کنید یا در [[ویکیواژه:فنی]] گزارش کنید.' // localized
});
if (Twinkle.getPref('speedySelectionStyle') !== 'radioClick') {
form.append({ type: 'submit', className: 'tw-speedy-submit' }); // Renamed in modeChanged
}
var result = form.render();
dialog.setContent(result);
dialog.display();
Twinkle.speedy.callback.modeChanged(result);
};
Twinkle.speedy.callback.getMode = function twinklespeedyCallbackGetMode(form) {
var mode = Twinkle.speedy.mode.userSingleSubmit;
if (form.tag_only && !form.tag_only.checked) {
if (form.delmultiple.checked) {
mode = Twinkle.speedy.mode.sysopMultipleSubmit;
} else {
mode = Twinkle.speedy.mode.sysopSingleSubmit;
}
} else {
if (form.multiple.checked) {
mode = Twinkle.speedy.mode.userMultipleSubmit;
} else {
mode = Twinkle.speedy.mode.userSingleSubmit;
}
}
if (Twinkle.getPref('speedySelectionStyle') === 'radioClick') {
mode++;
}
return mode;
};
Twinkle.speedy.callback.modeChanged = function twinklespeedyCallbackModeChanged(form) {
var namespace = mw.config.get('wgNamespaceNumber');
// first figure out what mode we're in
var mode = Twinkle.speedy.callback.getMode(form);
var isSysopMode = Twinkle.speedy.mode.isSysop(mode);
if (isSysopMode) {
$('[name=delete_options]').show();
$('[name=tag_options]').hide();
$('button.tw-speedy-submit').text('حذف صفحه'); // localized
} else {
$('[name=delete_options]').hide();
$('[name=tag_options]').show();
$('button.tw-speedy-submit').text('برچسب زدن به صفحه'); // localized
}
var work_area = new Morebits.quickForm.element({
type: 'div',
name: 'work_area'
});
if (mode === Twinkle.speedy.mode.userMultipleRadioClick || mode === Twinkle.speedy.mode.sysopMultipleRadioClick) {
var evaluateType = isSysopMode ? 'evaluateSysop' : 'evaluateUser';
work_area.append({
type: 'div',
label: 'هنگامی که کارتان تمام شد، کلیک کنید:' // localized
});
work_area.append({
type: 'button',
name: 'submit-multiple',
label: isSysopMode ? 'حذف صفحه' : 'برچسب زدن به صفحه', // localized
event: function(event) {
Twinkle.speedy.callback[evaluateType](event);
event.stopPropagation();
}
});
}
var radioOrCheckbox = Twinkle.speedy.mode.isMultiple(mode) ? 'checkbox' : 'radio';
if (isSysopMode && !Twinkle.speedy.mode.isMultiple(mode)) {
work_area.append({ type: 'header', label: 'دلیل دیگر' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.customRationale, mode) });
}
if (namespace % 2 === 1 && namespace !== 3) {
// show db-talk on talk pages, but not user talk pages
work_area.append({ type: 'header', label: 'صفحههای بحث' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.talkList, mode) });
}
if (!mw.config.get('wgIsRedirect')) {
switch (namespace) {
case 0: // article
case 1: // talk
work_area.append({ type: 'header', label: 'مدخلها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.articleList, mode) });
break;
case 2: // user
case 3: // user talk
work_area.append({ type: 'header', label: 'صفحههای کاربری' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.userList, mode) });
break;
case 6: // file
case 7: // file talk
work_area.append({ type: 'header', label: 'پروندهها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.fileList, mode) });
if (!isSysopMode) {
work_area.append({ type: 'div', label: 'افزودن برچسب برای معیارهای پ۴ (بدون مجوز)، پ۵ (پروندهٔ غیر آزاد استفاده نشده)، پ۶ (فاقد دلیل برای استفادهٔ منصفانه) و پ۱۱ (بدون مدرک برای مجوز) با استفاده از زبانهٔ «تصاویر» در توینکل ممکن است.' }); // localized
}
break;
case 10: // template
case 11: // template talk
work_area.append({ type: 'header', label: 'الگوها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.templateList, mode) });
break;
case 14: // category
case 15: // category talk
work_area.append({ type: 'header', label: 'ردهها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.categoryList, mode) });
break;
case 100: // portal
case 101: // portal talk
work_area.append({ type: 'header', label: 'درگاهها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.portalList, mode) });
break;
default:
break;
}
} else {
if (namespace === 2 || namespace === 3) {
work_area.append({ type: 'header', label: 'صفحههای کاربری' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.userList, mode) });
}
work_area.append({ type: 'header', label: 'تغییرمسیرها' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.redirectList, mode) });
}
var generalCriteria = Twinkle.speedy.generalList;
// custom rationale lives under general criteria when tagging
if (!isSysopMode) {
generalCriteria = Twinkle.speedy.customRationale.concat(generalCriteria);
}
work_area.append({ type: 'header', label: 'دلایل عمومی' }); // localized
work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(generalCriteria, mode) });
var old_area = Morebits.quickForm.getElements(form, 'work_area')[0];
form.replaceChild(work_area.render(), old_area);
// Drop any section header that ended up with no criteria beneath it
// (some namespace criteria lists are empty on Wiktionary).
$(form).find('h5').each(function() {
var has = false, $n = $(this).next();
while ($n.length && !$n.is('h5')) {
if ($n.find('input').length) { has = true; break; }
$n = $n.next();
}
if (!has) { $(this).remove(); }
});
// if sysop, check if CSD is already on the page and fill in custom rationale
if (isSysopMode && Twinkle.speedy.hasCSD) {
var customOption = $('input[name=csd][value=reason]')[0];
if (customOption) {
if (Twinkle.getPref('speedySelectionStyle') !== 'radioClick') {
// force listeners to re-init
customOption.click();
customOption.parentNode.appendChild(customOption.subgroup);
}
customOption.subgroup.querySelector('input').value = decodeURIComponent($('#delete-reason').text()).replace(/\+/g, ' ');
}
}
};
Twinkle.speedy.generateCsdList = function twinklespeedyGenerateCsdList(list, mode) {
// mode switches
var isSysopMode = Twinkle.speedy.mode.isSysop(mode);
var multiple = Twinkle.speedy.mode.isMultiple(mode);
var hasSubmitButton = Twinkle.speedy.mode.hasSubmitButton(mode);
var pageNamespace = mw.config.get('wgNamespaceNumber');
var openSubgroupHandler = function(e) {
$(e.target.form).find('input').prop('disabled', true);
$(e.target.form).children().css('color', 'gray');
$(e.target).parent().css('color', 'black').find('input').prop('disabled', false);
$(e.target).parent().find('input:text')[0].focus();
e.stopPropagation();
};
var submitSubgroupHandler = function(e) {
var evaluateType = Twinkle.speedy.mode.isSysop(mode) ? 'evaluateSysop' : 'evaluateUser';
Twinkle.speedy.callback[evaluateType](e);
e.stopPropagation();
};
return $.map(list, function(critElement) {
var criterion = $.extend({}, critElement);
if (multiple) {
if (criterion.hideWhenMultiple) {
return null;
}
if (criterion.hideSubgroupWhenMultiple) {
criterion.subgroup = null;
}
} else {
if (criterion.hideWhenSingle) {
return null;
}
if (criterion.hideSubgroupWhenSingle) {
criterion.subgroup = null;
}
}
if (isSysopMode) {
if (criterion.hideWhenSysop) {
return null;
}
if (criterion.hideSubgroupWhenSysop) {
criterion.subgroup = null;
}
} else {
if (criterion.hideWhenUser) {
return null;
}
if (criterion.hideSubgroupWhenUser) {
criterion.subgroup = null;
}
}
if (mw.config.get('wgIsRedirect') && criterion.hideWhenRedirect) {
return null;
}
if (criterion.showInNamespaces && criterion.showInNamespaces.indexOf(pageNamespace) < 0) {
return null;
}
if (criterion.hideInNamespaces && criterion.hideInNamespaces.indexOf(pageNamespace) > -1) {
return null;
}
if (criterion.subgroup && !hasSubmitButton) {
if (Array.isArray(criterion.subgroup)) {
criterion.subgroup = criterion.subgroup.concat({
type: 'button',
name: 'submit',
label: isSysopMode ? 'حذف صفحه' : 'برچسب زدن به صفحه', // localized
event: submitSubgroupHandler
});
} else {
criterion.subgroup = [
criterion.subgroup,
{
type: 'button',
name: 'submit', // ends up being called "csd.submit" so this is OK
label: isSysopMode ? 'حذف صفحه' : 'برچسب زدن به صفحه', // localized
event: submitSubgroupHandler
}
];
}
// FIXME: does this do anything?
criterion.event = openSubgroupHandler;
}
return criterion;
});
};
Twinkle.speedy.customRationale = [
{
label: 'دلیل دیگر' + (Morebits.userIsSysop ? ' (دلیل شخصیسازیشده برای حذف)' : ' با استفاده از الگوی {{حذف سریع}}'), // localized
value: 'reason',
tooltip: 'دست کم یکی از معیارهای حذف سریع باید برای صفحه مصداق داشته باشد، و شما این را باید در دلیلی که مینویسید ذکر کنید. این گزینه برای آن نیست که وقتی دلیل حذف را بین گزینههای دیگر نیافتید از آن استفاده کنید.', // localized
subgroup: {
name: 'reason_1',
type: 'input',
label: 'دلیل: ', // localized
size: 60
},
hideWhenMultiple: true
}
];
Twinkle.speedy.talkList = [
{
label: 'بحث یتیم: صفحهٔ بحثی بدون صفحهٔ اصلی',
value: 'talk',
reason: 'بحث یتیم',
tooltip: 'صفحهٔ بحثی که صفحهٔ اصلی متناظر آن وجود ندارد یا حذف شدهاست. شامل بایگانیهای بحث و صفحههای بحث پروندههای موجود در ویکیانبار نمیشود.'
}
];
Twinkle.speedy.fileList = [
{
label: 'تکراری: نسخهٔ یکسان در ویکیواژه یا ویکیانبار موجود است',
value: 'duplicate',
reason: 'تکراری',
tooltip: 'پروندهای که نسخهٔ کاملاً یکسان آن هماکنون در ویکیواژه یا ویکیانبار موجود است.'
},
{
label: 'استفاده نشده: پروندهٔ بیکاربرد',
value: 'unused',
reason: 'استفاده نشده',
tooltip: 'پروندهای که در هیچ صفحهای به کار نرفته و کاربردی برای آن متصور نیست.'
}
];
Twinkle.speedy.articleList = [];
Twinkle.speedy.categoryList = [
{
label: 'استفاده نشده: ردهٔ خالی و بیکاربرد',
value: 'unused',
reason: 'استفاده نشده',
tooltip: 'ردهای که خالی است و کاربردی برای آن متصور نیست.'
}
];
Twinkle.speedy.userList = [];
Twinkle.speedy.templateList = [
{
label: 'استفاده نشده: الگوی بیکاربرد',
value: 'unused',
reason: 'استفاده نشده',
tooltip: 'الگویی که در هیچ صفحهای به کار نرفته و کاربردی برای آن متصور نیست.'
}
];
Twinkle.speedy.portalList = [];
Twinkle.speedy.generalList = [
{
label: 'خرابکاری یا آزمایش محض',
value: 'vandalism',
reason: 'خرابکاری',
tooltip: 'صفحهای که تنها برای خرابکاری، آزمایش یا محتوای بیمعنا ساخته شدهاست.'
},
{
label: 'حذفشده: بازسازی صفحهٔ پیشتر حذفشده',
value: 'deleted',
reason: 'حذف شده',
tooltip: 'صفحهای که پیشتر حذف شده و بی هیچ تغییر معناداری دوباره ساخته شدهاست.'
},
{
label: 'نقض حق تکثیر',
value: 'copyvio',
reason: 'نقض حق تکثیر',
tooltip: 'صفحهای که آشکارا رونوشت از منبعی دارای حق تکثیر است و محتوای آزاد قابلنگهداری ندارد.',
subgroup: {
name: 'copyvio_url',
type: 'input',
label: 'نشانی منبع (اختیاری): ',
size: 60
}
},
{
label: 'تکراری: نسخهٔ یکسان موجود است',
value: 'duplicate',
reason: 'تکراری',
tooltip: 'مدخل یا صفحهای که نسخهٔ یکسان آن هماکنون در ویکیواژه موجود است.',
hideInNamespaces: [ 6, 14, 10 ]
},
{
label: 'منتقلشده به پروژهٔ دیگر ویکیمدیا',
value: 'moved',
reason: 'ویکیمدیا',
tooltip: 'محتوایی که به پروژهٔ دیگری از بنیاد ویکیمدیا منتقل شده و ماندن آن در ویکیواژه لازم نیست.'
}
];
Twinkle.speedy.redirectList = [];
Twinkle.speedy.normalizeHash = {
'reason': 'حذف سریع',
'vandalism': 'خرابکاری',
'deleted': 'حذف شده',
'copyvio': 'نقض حق تکثیر',
'duplicate': 'تکراری',
'unused': 'استفاده نشده',
'moved': 'ویکیمدیا',
'talk': 'بحث یتیم'
};
Twinkle.speedy.callbacks = {
getTemplateCodeAndParams: function(params) {
var reasonFor = function(value, tp) {
if (value === 'reason') {
return (tp && tp['1']) ? tp['1'] : '';
}
var r = Twinkle.speedy.normalizeHash[value] || value;
if (value === 'copyvio' && tp && tp.url) {
r += ' ([' + tp.url + '])';
}
return r;
};
var reasons = [];
$.each(params.values, function(index, value) {
var r = reasonFor(value, params.templateParams[index]);
if (r) {
reasons.push(r);
}
});
if (!reasons.length) {
reasons.push('حذف سریع');
}
var code;
if (reasons.length > 2) {
code = '{{حذف سریع|' + reasons[0] + '|' + reasons.slice(1).join('؛ ') + '}}';
} else {
code = '{{حذف سریع|' + reasons.join('|') + '}}';
}
params.utparams = { reason: reasons.join('؛ ') };
return [code, params.utparams];
},
parseWikitext: function(wikitext, callback) {
var query = {
action: 'parse',
prop: 'text',
pst: 'true',
text: wikitext,
contentmodel: 'wikitext',
title: mw.config.get('wgPageName')
};
var statusIndicator = new Morebits.status('ایجاد خلاصهٔ حذف'); // localized
var api = new Morebits.wiki.api('Parsing deletion template', query, function(apiObj) {
var reason = decodeURIComponent($(apiObj.getXML().querySelector('text').childNodes[0].nodeValue).find('#delete-reason').text()).replace(/\+/g, ' ');
if (!reason) {
statusIndicator.warn('امکان ایجاد خلاصه از الگوی حذف وجود ندارد'); // localized
} else {
statusIndicator.info('کامل'); // localized
}
callback(reason);
}, statusIndicator);
api.post();
},
noteToCreator: function(pageobj) {
var params = pageobj.getCallbackParameters();
var initialContrib = pageobj.getCreator();
// disallow notifying yourself
if (initialContrib === mw.config.get('wgUserName')) {
Morebits.status.warn('شما (' + initialContrib + ') این صفحه را ایجاد کردهاید؛ توقف آگاهسازی کاربر'); // localized
initialContrib = null;
// don't notify users when their user talk page is nominated/deleted
} else if (initialContrib === mw.config.get('wgTitle') && mw.config.get('wgNamespaceNumber') === 3) {
Morebits.status.warn('Notifying initial contributor: this user created their own user talk page; skipping notification');
initialContrib = null;
// quick hack to prevent excessive unwanted notifications, per request. Should actually be configurable on recipient page...
} else if ((initialContrib === 'Cyberbot I' || initialContrib === 'SoxBot') && params.normalizeds[0] === 'f2') {
Morebits.status.warn('Notifying initial contributor: page created procedurally by bot; skipping notification');
initialContrib = null;
// Check for already existing tags
} else if (Twinkle.speedy.hasCSD && params.warnUser && !confirm('The page is has a deletion-related tag, and thus the creator has likely been notified. Do you want to notify them for this deletion as well?')) {
Morebits.status.info('Notifying initial contributor', 'canceled by user; skipping notification.');
initialContrib = null;
}
if (initialContrib) {
var usertalkpage = new Morebits.wiki.page('User talk:' + initialContrib, 'Notifying initial contributor (' + initialContrib + ')'),
notifytext, i, editsummary;
notifytext = '\n{{جا:هشدار حذف سریع|' + Morebits.pageNameNorm + '|' + ((params.utparams && params.utparams.reason) || 'حذف سریع') + '}} ~~~~';
editsummary = 'آگاهسازی: ' + (params.warnUser ? '' : 'نامزدی ') + 'حذف سریع'; // localized
if (params.normalizeds.indexOf('g10') === -1) { // no article name in summary for G10 taggings
editsummary += ' [[:' + Morebits.pageNameNorm + ']].';
} else {
editsummary += ' صفحهٔ حمله.'; // localized
}
usertalkpage.setAppendText(notifytext);
usertalkpage.setEditSummary(editsummary + Twinkle.getPref('summaryAd'));
usertalkpage.setCreateOption('recreate');
usertalkpage.setFollowRedirect(true);
usertalkpage.append(function onNotifySuccess() {
// add this nomination to the user's userspace log, if the user has enabled it
if (params.lognomination) {
Twinkle.speedy.callbacks.user.addToLog(params, initialContrib);
}
}, function onNotifyError() {
// if user could not be notified, log nomination without mentioning that notification was sent
if (params.lognomination) {
Twinkle.speedy.callbacks.user.addToLog(params, null);
}
});
} else if (params.lognomination) {
// log nomination even if the user notification wasn't sent
Twinkle.speedy.callbacks.user.addToLog(params, null);
}
},
sysop: {
main: function(params) {
var reason;
if (!params.normalizeds.length && params.normalizeds[0] === 'db') {
reason = prompt('Enter the deletion summary to use, which will be entered into the deletion log:', '');
Twinkle.speedy.callbacks.sysop.deletePage(reason, params);
} else {
var code = Twinkle.speedy.callbacks.getTemplateCodeAndParams(params)[0];
Twinkle.speedy.callbacks.parseWikitext(code, function(reason) {
if (params.promptForSummary) {
reason = prompt('Enter the deletion summary to use, or press OK to accept the automatically generated one.', reason);
}
Twinkle.speedy.callbacks.sysop.deletePage(reason, params);
});
}
},
deletePage: function(reason, params) {
var thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'حذف صفحه'); // localized
if (reason === null) {
return Morebits.status.error('درخواست دلیل', 'لغو توسط کاربر'); // localized
} else if (!reason || !reason.replace(/^\s*/, '').replace(/\s*$/, '')) {
return Morebits.status.error('درخواست دلیل', "you didn't give one. I don't know... what with admins and their apathetic antics... I give up...");
}
var deleteMain = function(callback) {
thispage.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
thispage.deletePage(function() {
thispage.getStatusElement().info('انجام شد'); // localized
typeof callback === 'function' && callback();
Twinkle.speedy.callbacks.sysop.deleteTalk(params);
});
};
// look up initial contributor. If prompting user for deletion reason, just display a link.
// Otherwise open the talk page directly
if (params.warnUser) {
thispage.setCallbackParameters(params);
thispage.lookupCreation(function(pageobj) {
deleteMain(function() {
Twinkle.speedy.callbacks.noteToCreator(pageobj);
});
});
} else {
deleteMain();
}
},
deleteTalk: function(params) {
// delete talk page
if (params.deleteTalkPage &&
params.normalized !== 'f8' &&
document.getElementById('ca-talk').className !== 'new') {
var talkpage = new Morebits.wiki.page(mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceNumber') + 1] + ':' + mw.config.get('wgTitle'), 'حذف صفحهٔ بحث'); // localized
talkpage.setEditSummary('[[WP:CSD#G8|G8]]: Talk page of deleted page "' + Morebits.pageNameNorm + '"' + Twinkle.getPref('deletionSummaryAd'));
talkpage.deletePage();
// this is ugly, but because of the architecture of wiki.api, it is needed
// (otherwise success/failure messages for the previous action would be suppressed)
window.setTimeout(function() {
Twinkle.speedy.callbacks.sysop.deleteRedirects(params);
}, 1800);
} else {
Twinkle.speedy.callbacks.sysop.deleteRedirects(params);
}
},
deleteRedirects: function(params) {
// delete redirects
if (params.deleteRedirects) {
var query = {
'action': 'query',
'titles': mw.config.get('wgPageName'),
'prop': 'redirects',
'rdlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
var wikipedia_api = new Morebits.wiki.api('getting list of redirects...', query, Twinkle.speedy.callbacks.sysop.deleteRedirectsMain,
new Morebits.status('در حال حذف تغییرمسیرها')); // localized
wikipedia_api.params = params;
wikipedia_api.post();
}
// promote Unlink tool
var $link, $bigtext;
if (mw.config.get('wgNamespaceNumber') === 6 && params.normalized !== 'f8') {
$link = $('<a/>', {
'href': '#',
'text': 'click here to go to the Unlink tool',
'css': { 'fontSize': '130%', 'fontWeight': 'bold' },
'click': function() {
Morebits.wiki.actionCompleted.redirect = null;
Twinkle.speedy.dialog.close();
Twinkle.unlink.callback('Removing usages of and/or links to deleted file ' + Morebits.pageNameNorm);
}
});
$bigtext = $('<span/>', {
'text': 'To orphan backlinks and remove instances of file usage',
'css': { 'fontSize': '130%', 'fontWeight': 'bold' }
});
Morebits.status.info($bigtext[0], $link[0]);
} else if (params.normalized !== 'f8') {
$link = $('<a/>', {
'href': '#',
'text': 'click here to go to the Unlink tool',
'css': { 'fontSize': '130%', 'fontWeight': 'bold' },
'click': function() {
Morebits.wiki.actionCompleted.redirect = null;
Twinkle.speedy.dialog.close();
Twinkle.unlink.callback('Removing links to deleted page ' + Morebits.pageNameNorm);
}
});
$bigtext = $('<span/>', {
'text': 'To orphan backlinks',
'css': { 'fontSize': '130%', 'fontWeight': 'bold' }
});
Morebits.status.info($bigtext[0], $link[0]);
}
},
deleteRedirectsMain: function(apiobj) {
var xmlDoc = apiobj.getXML();
var $snapshot = $(xmlDoc).find('redirects rd');
var total = $snapshot.length;
var statusIndicator = apiobj.statelem;
if (!total) {
statusIndicator.status('هیچ تغییرمسیری پیدا نشد'); // localized
return;
}
statusIndicator.status('0%');
var current = 0;
var onsuccess = function(apiobjInner) {
var now = parseInt(100 * ++current / total, 10) + '%';
statusIndicator.update(now);
apiobjInner.statelem.unlink();
if (current >= total) {
statusIndicator.info(now + ' (کامل شد)'); // localized
Morebits.wiki.removeCheckpoint();
}
};
Morebits.wiki.addCheckpoint();
$snapshot.each(function(key, value) {
var title = $(value).attr('title');
var page = new Morebits.wiki.page(title, 'در حال حذف تغییرمسیر "' + title + '"'); // localized
page.setEditSummary('[[WP:CSD#G8|G8]]: Redirect to deleted page "' + Morebits.pageNameNorm + '"' + Twinkle.getPref('deletionSummaryAd'));
page.deletePage(onsuccess);
});
}
},
user: {
main: function(pageobj) {
var statelem = pageobj.getStatusElement();
// defaults to /doc for lua modules, which may not exist
if (!pageobj.exists() && mw.config.get('wgPageContentModel') !== 'Scribunto') {
statelem.error("It seems that the page doesn't exist; perhaps it has already been deleted");
return;
}
var text = pageobj.getPageText();
var params = pageobj.getCallbackParameters();
statelem.status('در حال بررسی برچسبهای روی صفحه...'); // localized
// check for existing deletion tags
var tag = /(?:\{\{\s*(db|delete|db-.*?|speedy deletion-.*?)(?:\s*\||\s*\}\}))/.exec(text);
// This won't make use of the db-multiple template but it probably should
if (tag && !confirm('The page already has the CSD-related template {{' + tag[1] + '}} on it. Do you want to add another CSD template?')) {
return;
}
var xfd = /\{\{((?:article for deletion|proposed deletion|prod blp|template for discussion)\/dated|[cfm]fd\b)/i.exec(text) || /#invoke:(RfD)/.exec(text);
if (xfd && !confirm('The deletion-related template {{' + xfd[1] + '}} was found on the page. Do you still want to add a CSD template?')) {
return;
}
// given the params, builds the template and also adds the user talk page parameters to the params that were passed in
// returns => [<string> wikitext, <object> utparams]
var buildData = Twinkle.speedy.callbacks.getTemplateCodeAndParams(params),
code = buildData[0];
params.utparams = buildData[1];
// curate/patrol the page
if (Twinkle.getPref('markSpeedyPagesAsPatrolled')) {
pageobj.triage();
}
// Wrap SD template in noinclude tags if we are in template space.
// Won't work with userboxes in userspace, or any other transcluded page outside template space
if (mw.config.get('wgNamespaceNumber') === 10) { // Template:
code = '<noinclude>' + code + '</noinclude>';
}
// Remove tags that become superfluous with this action
text = text.replace(/\{\{\s*([Uu]serspace draft)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/g, '');
if (mw.config.get('wgNamespaceNumber') === 6) {
// remove "move to Commons" tag - deletion-tagged files cannot be moved to Commons
text = text.replace(/\{\{(mtc|(copy |move )?to ?commons|move to wikimedia commons|copy to wikimedia commons)[^}]*\}\}/gi, '');
}
if (params.requestsalt) {
if (params.normalizeds.indexOf('g10') === -1) {
code = code + '\n{{نمک}}'; // localized
} else {
code = '{{نمک}}\n' + code; // localized
}
}
// Generate edit summary for edit
var editsummary;
if (params.normalizeds.length > 1) {
editsummary = 'ثبت درخواست حذف سریع ('; // localized
$.each(params.normalizeds, function(index, norm) {
editsummary += '[[ویکیواژه:محس#' + norm.toUpperCase() + '|محس ' + norm.toUpperCase() + ']], '; // localized
});
editsummary = editsummary.substr(0, editsummary.length - 2); // remove trailing comma
editsummary += ').';
} else if (params.normalizeds[0] === 'db') {
editsummary = 'ثبت درخواست [[ویکیواژه:محس|حذف سریع]] با معیار «' + params.templateParams[0]['1'] + '».'; // localized
} else {
editsummary = 'ثبت درخواست حذف سریع ([[ویکیواژه:محس#' + params.normalizeds[0].toUpperCase() + '|محس ' + params.normalizeds[0].toUpperCase() + ']]).'; // localized
}
// Set the correct value for |ts= parameter in {{db-g13}}
if (params.normalizeds.indexOf('g13') !== -1) {
code = code.replace('$TIMESTAMP', pageobj.getLastEditTime());
}
pageobj.setPageText(code + (params.normalizeds.indexOf('g10') !== -1 ? '' : '\n' + text)); // cause attack pages to be blanked
pageobj.setEditSummary(editsummary + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(params.watch);
if (params.scribunto) {
pageobj.setCreateOption('recreate'); // Module /doc might not exist
if (params.watch) {
// Watch module in addition to /doc subpage
var watch_query = {
action: 'watch',
titles: mw.config.get('wgPageName'),
token: mw.user.tokens.get('watchToken')
};
new Morebits.wiki.api('Adding Module to watchlist', watch_query).post();
}
}
pageobj.save(Twinkle.speedy.callbacks.user.tagComplete);
},
tagComplete: function(pageobj) {
var params = pageobj.getCallbackParameters();
// Notification to first contributor, will also log nomination to the user's userspace log
if (params.usertalk) {
var thispage = new Morebits.wiki.page(Morebits.pageNameNorm);
thispage.setCallbackParameters(params);
thispage.lookupCreation(Twinkle.speedy.callbacks.noteToCreator);
// or, if not notifying, add this nomination to the user's userspace log without the initial contributor's name
} else if (params.lognomination) {
Twinkle.speedy.callbacks.user.addToLog(params, null);
}
},
// note: this code is also invoked from twinkleimage
// the params used are:
// for CSD: params.values, params.normalizeds (note: normalizeds is an array)
// for DI: params.fromDI = true, params.templatename, params.normalized (note: normalized is a string)
addToLog: function(params, initialContrib) {
var usl = new Morebits.userspaceLogger(Twinkle.getPref('speedyLogPageName'));
usl.initialText =
"This is a log of all [[WP:CSD|speedy deletion]] nominations made by this user using [[WP:TW|Twinkle]]'s CSD module.\n\n" +
'If you no longer wish to keep this log, you can turn it off using the [[Wikipedia:Twinkle/Preferences|preferences panel]], and ' +
'nominate this page for speedy deletion under [[WP:CSD#U1|CSD U1]].' +
(Morebits.userIsSysop ? '\n\nThis log does not track outright speedy deletions made using Twinkle.' : '');
var formatParamLog = function(normalize, csdparam, input) {
if ((normalize === 'G4' && csdparam === 'xfd') ||
(normalize === 'G6' && csdparam === 'page') ||
(normalize === 'G6' && csdparam === 'fullvotepage') ||
(normalize === 'G6' && csdparam === 'sourcepage') ||
(normalize === 'A2' && csdparam === 'source') ||
(normalize === 'A10' && csdparam === 'article') ||
(normalize === 'F1' && csdparam === 'filename') ||
(normalize === 'F5' && csdparam === 'replacement')) {
input = '[[:' + input + ']]';
} else if (normalize === 'G5' && csdparam === 'user') {
input = '[[:User:' + input + ']]';
} else if (normalize === 'G12' && csdparam.lastIndexOf('url', 0) === 0 && input.lastIndexOf('http', 0) === 0) {
input = '[' + input + ' ' + input + ']';
} else if (normalize === 'T3' && csdparam === 'template') {
input = '[[:Template:' + input + ']]';
} else if (normalize === 'F8' && csdparam === 'filename') {
input = '[[commons:' + input + ']]';
} else if (normalize === 'P1' && csdparam === 'criterion') {
input = '[[WP:CSD#' + input + ']]';
}
return ' {' + normalize + ' ' + csdparam + ': ' + input + '}';
};
var extraInfo = '';
// If a logged file is deleted but exists on commons, the wikilink will be blue, so provide a link to the log
var fileLogLink = mw.config.get('wgNamespaceNumber') === 6 ? ' ([{{fullurl:Special:Log|page=' + mw.util.wikiUrlencode(mw.config.get('wgPageName')) + '}} log])' : '';
var editsummary = 'Logging speedy deletion nomination';
var appendText = '# [[:' + Morebits.pageNameNorm;
if (params.fromDI) {
appendText += ']]' + fileLogLink + ': DI [[WP:CSD#' + params.normalized.toUpperCase() + '|CSD ' + params.normalized.toUpperCase() + ']] ({{tl|di-' + params.templatename + '}})';
// The params data structure when coming from DI is quite different,
// so this hardcodes the only interesting items worth logging
['reason', 'replacement', 'source'].forEach(function(item) {
if (params[item]) {
extraInfo += formatParamLog(params.normalized.toUpperCase(), item, params[item]);
return false;
}
});
editsummary += ' [[:' + Morebits.pageNameNorm + ']].'; // localized
} else {
if (params.normalizeds.indexOf('g10') === -1) { // no article name in log for G10 taggings
appendText += ']]' + fileLogLink + ': ';
editsummary += ' [[:' + Morebits.pageNameNorm + ']].'; // localized
} else {
appendText += '|این]] صفحهٔ حمله' + fileLogLink + ': '; // localized
editsummary += ' یک صفحهٔ حمله.'; // localized
}
if (params.normalizeds.length > 1) {
appendText += 'چند معیار ('; // localized
$.each(params.normalizeds, function(index, norm) {
appendText += '[[WP:CSD#' + norm.toUpperCase() + '|' + norm.toUpperCase() + ']], ';
});
appendText = appendText.substr(0, appendText.length - 2); // remove trailing comma
appendText += ')';
} else if (params.normalizeds[0] === 'db') {
appendText += '{{tl|db-reason}}';
} else {
appendText += '[[WP:CSD#' + params.normalizeds[0].toUpperCase() + '|CSD ' + params.normalizeds[0].toUpperCase() + ']] ({{tl|db-' + params.values[0] + '}})';
}
// If params is "empty" it will still be full of empty arrays, but ask anyway
if (params.templateParams) {
// Treat custom rationale individually
if (params.normalizeds[0] && params.normalizeds[0] === 'db') {
extraInfo += formatParamLog('Custom', 'rationale', params.templateParams[0]['1']);
} else {
params.templateParams.forEach(function(item, index) {
var keys = Object.keys(item);
if (keys[0] !== undefined && keys[0].length > 0) {
// Second loop required since some items (G12, F9) may have multiple keys
keys.forEach(function(key, keyIndex) {
if (keys[keyIndex] === 'blanked' || keys[keyIndex] === 'ts') {
return true; // Not worth logging
}
extraInfo += formatParamLog(params.normalizeds[index].toUpperCase(), keys[keyIndex], item[key]);
});
}
});
}
}
}
if (params.requestsalt) {
appendText += '; حفاظت در برابر ایجاد درخواست شدهاست ([[ویکیواژه:نمک|نمکخورده]])'; // localized
}
if (extraInfo) {
appendText += '؛ اطلاعات اضافی:' + extraInfo; // localized
}
if (initialContrib) {
appendText += '؛ {{user|1=' + initialContrib + '}} آگاه شد'; // localized
}
appendText += ' ~~~~~\n';
usl.log(appendText, editsummary + Twinkle.getPref('summaryAd'));
}
}
};
// validate subgroups in the form passed into the speedy deletion tag
Twinkle.speedy.getParameters = function twinklespeedyGetParameters(form, values) {
var parameters = [];
$.each(values, function(index, value) {
var currentParams = [];
switch (value) {
case 'reason':
if (form['csd.reason_1']) {
var dbrationale = form['csd.reason_1'].value;
if (!dbrationale || !dbrationale.trim()) {
alert('دلیل دلخواه: لطفاً دلیلی ارائه کنید.'); // localized
parameters = null;
return false;
}
currentParams['1'] = dbrationale;
}
break;
case 'userreq': // U1
if (form['csd.userreq_rationale']) {
var u1rationale = form['csd.userreq_rationale'].value;
if (mw.config.get('wgNamespaceNumber') === 3 && !(/\//).test(mw.config.get('wgTitle')) &&
(!u1rationale || !u1rationale.trim())) {
alert('CSD U1: Please specify a rationale when nominating user talk pages.');
parameters = null;
return false;
}
currentParams.rationale = u1rationale;
}
break;
case 'repost': // G4
if (form['csd.repost_xfd']) {
var deldisc = form['csd.repost_xfd'].value;
if (deldisc) {
if (!/^(?:وپ|ویکیپدیا):/i.test(deldisc)) { // localized
alert('محس ع۴: صفحه نظرخواهی حذف باید با "ویکیواژه:" آغاز شود.'); // localized
parameters = null;
return false;
}
currentParams.xfd = deldisc;
}
}
break;
case 'banned': // G5
if (form['csd.banned_user'] && form['csd.banned_user'].value) {
currentParams.user = form['csd.banned_user'].value.replace(/^\s*کاربر:/i, ''); // localized
}
break;
case 'move': // G6
if (form['csd.move_page'] && form['csd.move_reason']) {
var movepage = form['csd.move_page'].value,
movereason = form['csd.move_reason'].value;
if (!movepage || !movepage.trim()) {
alert('محس ع۶ (انتقال): لطفاً نام صفحهای که باید به اینجا انتقال یابد مشخص کنید.'); // localized
parameters = null;
return false;
}
if (!movereason || !movereason.trim()) {
alert('محس ع۶ (انتقال): لطفاً دلیل انتقال را مشخص کنید.'); // localized
parameters = null;
return false;
}
currentParams.page = movepage;
currentParams.reason = movereason;
}
break;
case 'xfd': // G6
if (form['csd.xfd_fullvotepage']) {
var xfd = form['csd.xfd_fullvotepage'].value;
if (xfd) {
if (!/^(?:wp|wikipedia):/i.test(xfd)) {
alert('CSD G6 (XFD): The deletion discussion page name, if provided, must start with "Wikipedia:".');
parameters = null;
return false;
}
currentParams.fullvotepage = xfd;
}
}
break;
case 'copypaste': // G6
if (form['csd.copypaste_sourcepage']) {
var copypaste = form['csd.copypaste_sourcepage'].value;
if (!copypaste || !copypaste.trim()) {
alert('محس ع۶ (کپیپیست): لطفاً نام صفحهٔ مبدأ را مشخص کنید.'); // localized
parameters = null;
return false;
}
currentParams.sourcepage = copypaste;
}
break;
case 'g6': // G6
if (form['csd.g6_rationale'] && form['csd.g6_rationale'].value) {
currentParams.rationale = form['csd.g6_rationale'].value;
}
break;
case 'author': // G7
if (form['csd.author_rationale'] && form['csd.author_rationale'].value) {
currentParams.rationale = form['csd.author_rationale'].value;
}
break;
case 'g8': // G8
if (form['csd.g8_rationale'] && form['csd.g8_rationale'].value) {
currentParams.rationale = form['csd.g8_rationale'].value;
}
break;
case 'attack': // G10
currentParams.blanked = 'yes';
// it is actually blanked elsewhere in code, but setting the flag here
break;
case 'copyvio': // G12
if (form['csd.copyvio_url'] && form['csd.copyvio_url'].value) {
currentParams.url = form['csd.copyvio_url'].value;
}
if (form['csd.copyvio_url2'] && form['csd.copyvio_url2'].value) {
currentParams.url2 = form['csd.copyvio_url2'].value;
}
if (form['csd.copyvio_url3'] && form['csd.copyvio_url3'].value) {
currentParams.url3 = form['csd.copyvio_url3'].value;
}
break;
case 'afc': // G13
currentParams.ts = '$TIMESTAMP'; // to be replaced by the last revision timestamp when page is saved
break;
case 'redundantimage': // F1
if (form['csd.redundantimage_filename']) {
var redimage = form['csd.redundantimage_filename'].value;
if (!redimage || !redimage.trim()) {
alert('CSD F1: Please specify the filename of the other file.');
parameters = null;
return false;
}
currentParams.filename = /^\s*(Image|File):/i.test(redimage) ? redimage : 'File:' + redimage;
}
break;
case 'badfairuse': // F7
if (form['csd.badfairuse_rationale'] && form['csd.badfairuse_rationale'].value) {
currentParams.rationale = form['csd.badfairuse_rationale'].value;
}
break;
case 'commons': // F8
if (form['csd.commons_filename']) {
var filename = form['csd.commons_filename'].value;
if (filename && filename.trim() && filename !== Morebits.pageNameNorm) {
currentParams.filename = /^\s*(Image|File):/i.test(filename) ? filename : 'File:' + filename;
}
}
break;
case 'imgcopyvio': // F9
if (form['csd.imgcopyvio_url'] && form['csd.imgcopyvio_rationale']) {
var f9url = form['csd.imgcopyvio_url'].value;
var f9rationale = form['csd.imgcopyvio_rationale'].value;
if ((!f9url || !f9url.trim()) && (!f9rationale || !f9rationale.trim())) {
alert('CSD F9: You must enter a url or reason (or both) when nominating a file under F9.');
parameters = null;
return false;
}
if (form['csd.imgcopyvio_url'].value) {
currentParams.url = f9url;
}
if (form['csd.imgcopyvio_rationale'].value) {
currentParams.rationale = f9rationale;
}
}
break;
case 'foreign': // A2
if (form['csd.foreign_source']) {
var foreignlink = form['csd.foreign_source'].value;
if (!foreignlink || !foreignlink.trim()) {
alert('CSD A2: Please specify an interwiki link to the article of which this is a copy.');
parameters = null;
return false;
}
currentParams.source = foreignlink;
}
break;
case 'transwiki': // A5
if (form['csd.transwiki_location'] && form['csd.transwiki_location'].value) {
currentParams.location = form['csd.transwiki_location'].value;
}
break;
case 'a10': // A10
if (form['csd.a10_article']) {
var duptitle = form['csd.a10_article'].value;
if (!duptitle || !duptitle.trim()) {
alert('محس م۱۰: لطفاً نام مقالهٔ تکرار شده را مشخص کنید.'); // localized
parameters = null;
return false;
}
currentParams.article = duptitle;
}
break;
case 'policy': // T2
if (form['csd.policy_rationale'] && form['csd.policy_rationale'].value) {
currentParams.rationale = form['csd.policy_rationale'].value;
}
break;
case 'duplicatetemplate': // T3
if (form['csd.duplicatetemplate_2']) {
var t3template = form['csd.duplicatetemplate_2'].value;
if (!t3template || !t3template.trim()) {
alert('محس ال۳: لطفاً نام الگوی تکرار شده را مشخص کنید.'); // localized
parameters = null;
return false;
}
currentParams.ts = '~~~~~';
currentParams.template = t3template.replace(/^\s*Template:/i, '');
}
break;
case 'p1': // P1
if (form['csd.p1_criterion']) {
var criterion = form['csd.p1_criterion'].value;
if (!criterion || !criterion.trim()) {
alert('CSD P1: Please specify a single criterion.');
parameters = null;
return false;
}
currentParams.criterion = criterion;
}
break;
default:
break;
}
parameters.push(currentParams);
});
return parameters;
};
// Function for processing talk page notification template parameters
// key1/value1: for {{db-criterion-[notice|deleted]}} (via {{db-csd-[notice|deleted]-custom}})
// utparams.param: for {{db-[notice|deleted]-multiple}}
Twinkle.speedy.getUserTalkParameters = function twinklespeedyGetUserTalkParameters(normalized, parameters) {
var utparams = [];
// Special cases
if (normalized === 'db') {
utparams['2'] = parameters['1'];
} else if (normalized === 'g6') {
utparams.key1 = 'to';
utparams.value1 = Morebits.pageNameNorm;
} else if (normalized === 'g12') {
['url', 'url2', 'url3'].forEach(function(item, idx) {
if (parameters[item]) {
idx++;
utparams['key' + idx] = item;
utparams['value' + idx] = utparams[item] = parameters[item];
}
});
} else {
// Handle the rest
var param;
switch (normalized) {
case 'g4':
param = 'xfd';
break;
case 'a2':
param = 'source';
break;
case 'a5':
param = 'location';
break;
case 'a10':
param = 'article';
break;
case 'f9':
param = 'url';
break;
case 'p1':
param = 'criterion';
break;
default:
break;
}
// No harm in providing a usertalk template with the others' parameters
if (param && parameters[param]) {
utparams.key1 = param;
utparams.value1 = utparams[param] = parameters[param];
}
}
return utparams;
};
Twinkle.speedy.resolveCsdValues = function twinklespeedyResolveCsdValues(e) {
var values = (e.target.form ? e.target.form : e.target).getChecked('csd');
if (values.length === 0) {
alert('Please select a criterion!');
return null;
}
return values;
};
Twinkle.speedy.callback.evaluateSysop = function twinklespeedyCallbackEvaluateSysop(e) {
var form = e.target.form ? e.target.form : e.target;
if (e.target.type === 'checkbox' || e.target.type === 'text' ||
e.target.type === 'select') {
return;
}
var tag_only = form.tag_only;
if (tag_only && tag_only.checked) {
Twinkle.speedy.callback.evaluateUser(e);
return;
}
var values = Twinkle.speedy.resolveCsdValues(e);
if (!values) {
return;
}
var templateParams = Twinkle.speedy.getParameters(form, values);
if (!templateParams) {
return;
}
var normalizeds = values.map(function(value) {
return Twinkle.speedy.normalizeHash[value];
});
// analyse each criterion to determine whether to watch the page, prompt for summary, or notify the creator
var watchPage, promptForSummary;
normalizeds.forEach(function(norm) {
if (Twinkle.getPref('watchSpeedyPages').indexOf(norm) !== -1) {
watchPage = true;
}
if (Twinkle.getPref('promptForSpeedyDeletionSummary').indexOf(norm) !== -1) {
promptForSummary = true;
}
});
var warnusertalk = false;
if (form.warnusertalk.checked) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('warnUserOnSpeedyDelete').indexOf(norm) !== -1) {
if (norm === 'g6' && values[index] !== 'copypaste') {
return true;
}
warnusertalk = true;
return false; // break
}
});
}
var welcomeuser = false;
if (warnusertalk) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('welcomeUserOnSpeedyDeletionNotification').indexOf(norm) !== -1) {
welcomeuser = true;
return false; // break
}
});
}
var params = {
values: values,
normalizeds: normalizeds,
watch: watchPage,
deleteTalkPage: form.talkpage && form.talkpage.checked,
deleteRedirects: form.redirects.checked,
warnUser: warnusertalk,
welcomeuser: welcomeuser,
promptForSummary: promptForSummary,
templateParams: templateParams
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Twinkle.speedy.callbacks.sysop.main(params);
};
Twinkle.speedy.callback.evaluateUser = function twinklespeedyCallbackEvaluateUser(e) {
var form = e.target.form ? e.target.form : e.target;
if (e.target.type === 'checkbox' || e.target.type === 'text' ||
e.target.type === 'select') {
return;
}
var values = Twinkle.speedy.resolveCsdValues(e);
if (!values) {
return;
}
var templateParams = Twinkle.speedy.getParameters(form, values);
if (!templateParams) {
return;
}
// var multiple = form.multiple.checked;
var normalizeds = [];
$.each(values, function(index, value) {
var norm = Twinkle.speedy.normalizeHash[value];
normalizeds.push(norm);
});
// analyse each criterion to determine whether to watch the page/notify the creator
var watchPage = false;
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('watchSpeedyPages').indexOf(norm) !== -1) {
watchPage = true;
return false; // break
}
});
var notifyuser = false;
if (form.notify.checked) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('notifyUserOnSpeedyDeletionNomination').indexOf(norm) !== -1) {
if (norm === 'g6' && values[index] !== 'copypaste') {
return true;
}
notifyuser = true;
return false; // break
}
});
}
var welcomeuser = false;
if (notifyuser) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('welcomeUserOnSpeedyDeletionNotification').indexOf(norm) !== -1) {
welcomeuser = true;
return false; // break
}
});
}
var csdlog = false;
if (Twinkle.getPref('logSpeedyNominations')) {
$.each(normalizeds, function(index, norm) {
if (Twinkle.getPref('noLogOnSpeedyNomination').indexOf(norm) === -1) {
csdlog = true;
return false; // break
}
});
}
var params = {
values: values,
normalizeds: normalizeds,
watch: watchPage,
usertalk: notifyuser,
welcomeuser: welcomeuser,
lognomination: csdlog,
requestsalt: form.salting.checked,
templateParams: templateParams
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'Tagging complete';
// Modules can't be tagged, follow standard at TfD and place on /doc subpage
params.scribunto = mw.config.get('wgPageContentModel') === 'Scribunto';
var wikipedia_page = params.scribunto ? new Morebits.wiki.page(mw.config.get('wgPageName') + '/doc', 'Tagging module documentation page') : new Morebits.wiki.page(mw.config.get('wgPageName'), 'Tagging page');
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.speedy.callbacks.user.main);
};
})(jQuery);
// </nowiki>
pxc4vc0w29f3hs511abhyjajtw646qf
کاربر:Mamouri/twinkle/twinkleprod-2020.js
2
219492
908999
2026-07-30T16:04:50Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
908999
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** twinkleprod.js: PROD module
****************************************
* Mode of invocation: Tab ("PROD")
* Active on: Existing articles, files, books which are not redirects,
* and user pages in [[:Category:Wikipedia books (user books)]]
*/
Twinkle.prod = function twinkleprod() {
if (([0, 6].indexOf(mw.config.get('wgNamespaceNumber')) === -1)
|| !mw.config.get('wgCurRevisionId') || Morebits.wiki.isPageRedirect()) {
return;
}
Twinkle.addPortletLink(Twinkle.prod.callback, 'پیشنهاد حذف', 'tw-prod', 'پیشنهاد حذف طبق وو:پح'); // localized
};
// Used in edit summaries, for comparisons, etc.
var namespace;
Twinkle.prod.callback = function twinkleprodCallback() {
Twinkle.prod.defaultReason = Twinkle.getPref('prodReasonDefault');
switch (mw.config.get('wgNamespaceNumber')) {
case 0:
namespace = 'مقاله'; // localized
break;
case 6:
namespace = 'پرونده'; // localized
break;
}
var Window = new Morebits.simpleWindow(800, 410);
Window.setTitle('پیشنهاد حذف'); // localized
Window.setScriptName('توینکل'); // localiezd
var form = new Morebits.quickForm(Twinkle.prod.callback.evaluate);
Window.addFooterLink('سیاست پیشنهاد حذف', 'وو:پح'); // localized
// prod reason controls
var field = form.append({
type: 'field',
label: 'پارامترها', // localized
name: 'parameters'
});
field.append({
type: 'checkbox',
list: [
{
label: 'آگاه کردن سازندهٔ صفحه در صورت امکان', // localized
value: 'notify',
name: 'notify',
tooltip: "در صورت انتخاب این گزینه در صفحه بحث نویسنده مقاله، پیام اطلاعرسانی درج میشود", // localized
checked: true
}
]
});
field.append({
type: 'textarea',
name: 'reason',
label: 'دلیل برای پیشنهاد حذف:', // localized
value: Twinkle.prod.defaultReason
});
Window.addFooterLink('راهنمای توینکل', 'وو:توینکل/توضیحات'); // localized
form.append({ type: 'submit', label: 'پیشنهاد حذف' }); // localized
var result = form.render();
Window.setContent(result);
Window.display();
};
Twinkle.prod.callbacks = {
checkpriors: function(apiobj) {
var params = apiobj.params;
var ts = new Morebits.wiki.page(mw.config.get('wgPageName'));
ts.setFollowRedirect(true); // for NPP, and also because redirects are ineligible for PROD
ts.setCallbackParameters(params);
ts.setLookupNonRedirectCreator(true); // Look for author of first non-redirect revision
ts.lookupCreation(Twinkle.prod.callbacks.creationInfo);
},
creationInfo: function(pageobj) {
var params = pageobj.getCallbackParameters();
params.initialContrib = pageobj.getCreator();
params.creation = pageobj.getCreationTimestamp();
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'برچسب زدن تمام شد'; // localized
var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'برچسب زدن به صفحه'); // localized
wikipedia_page.setFollowRedirect(true); // for NPP, and also because redirects are ineligible for PROD
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.prod.callbacks.main);
},
main: function(pageobj) {
var statelem = pageobj.getStatusElement();
if (!pageobj.exists()) {
statelem.error("به نظر میآید که صفحه وجود ندارد. احتمالا قبلا حذف شده است."); // localized
return;
}
var text = pageobj.getPageText();
var params = pageobj.getCallbackParameters();
// Check for already existing deletion tags
var tag_re = /{{(?:db-?|delete|حذف سریع|حذف زماندار|پیشنهاد حذف|نظرخواهی برای حذف|article for deletion\/dated|ffd\b)|#invoke:RfD/i; // localized
if (tag_re.test(text)) {
statelem.warn('صفحه در حال حاضر برچسب حذف دارد. نامزدی پیشنهاد حذف متوقف شد'); // localized
return;
}
// Remove tags that become superfluous with this action
text = text.replace(/{{\s*(userspace draft|پیشنویس صفحه کاربری|mtc|(copy|move) to wikimedia commons|انتقال به (انبار|ویکیانبار)|(copy |move )?to ?commons)\s*(\|(?:{{[^{}]*}}|[^{}])*)?}}\s*/gi, '');
var prod_re = /{{\s*پیشنهاد حذف\s*[|}]/i; // localized
var summaryText;
if (!prod_re.test(text)) {
// Notification to first contributor
if (params.usertalk) {
// Disallow warning yourself
if (params.initialContrib === mw.config.get('wgUserName')) {
statelem.warn('خود شما (' + params.initialContrib + ') را ایجاد کردهاید؛ توقف اطلاعرسانی به کاربر'); // localized
if (Twinkle.getPref('logProdPages')) {
Twinkle.prod.callbacks.addToLog(params);
}
} else {
var notifytext = '\n== پیشنهاد حذف [[:' + Morebits.pageNameNorm + ']] ==\n' +
'صفحهٔ [[:' + Morebits.pageNameNorm + ']] برای حذف پیشنهاد شدهاست. دلیل: ' + params.reason + ' لطفاً اگر مخالفید نظرتان را بیان کنید. ~~~~\n'; // localized
var usertalkpage = new Morebits.wiki.page('بحث کاربر:' + params.initialContrib, 'اطلاعرسانی به سازنده صفحه (' + params.initialContrib + ')'); // localized
usertalkpage.setAppendText(notifytext);
usertalkpage.setEditSummary('اطلاعرسانی: پیشنهاد حذف [[:' + Morebits.pageNameNorm + ']].' + Twinkle.getPref('summaryAd')); // localized
usertalkpage.setCreateOption('recreate');
usertalkpage.setFollowRedirect(true);
usertalkpage.setCallbackParameters(params);
usertalkpage.append(function onNotifySuccess() {
// add nomination to the userspace log, if the user has enabled it
if (Twinkle.getPref('logProdPages')) {
params.logInitialContrib = params.initialContrib;
Twinkle.prod.callbacks.addToLog(params);
}
}, function onNotifyError() {
// if user could not be notified, log nomination without mentioning that notification was sent
if (Twinkle.getPref('logProdPages')) {
Twinkle.prod.callbacks.addToLog(params);
}
});
}
} else if (Twinkle.getPref('logProdPages')) { // If not notifying, log this PROD
Twinkle.prod.callbacks.addToLog(params);
}
summaryText = 'نامزد کردن ' + namespace + ' برای پیشنهاد حذف طبق [[وو:پح]].'; // localized
text = '{{پیشنهاد حذف|1=' + Morebits.string.formatReasonText(params.reason) + '}}\n' + text; // localized
}
// curate/patrol the page
if (Twinkle.getPref('markProdPagesAsPatrolled')) {
pageobj.triage();
}
pageobj.setPageText(text);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchProdPages'));
pageobj.setCreateOption('nocreate');
pageobj.save();
},
addToLog: function(params) {
var usl = new Morebits.userspaceLogger(Twinkle.getPref('prodLogPageName'));
usl.initialText =
"این صفحه فهرستی از مواردی که این کاربر با استفاده از [[وو:توینکل|توینکل]] برای [[وو:پح|پیشنهاد حذف]] نامزد کردهاست را نشان میدهد.\n\n" + // localized
'اگر نمیخواهید موارد جدید به این سیاهه اضافه شوند، آن را از طریق [[وو:توینکل تر|ترجیحات توینکل]] غیرفعال کنید' + // localized
'و اگر میخواهید سیاهه کلاً حذف شود آن را بر اساس معیار [[وو:محس#ک۱|ک۱]] نامزد حذف سریع کنید.\n'; // localized
var logText = '# [[:' + Morebits.pageNameNorm + ']]';
var summaryText;
// If a logged file is deleted but exists on commons, the wikilink will be blue, so provide a link to the log
logText += namespace === 'پرونده' ? ' ([{{fullurl:Special:Log|page=' + mw.util.wikiUrlencode(mw.config.get('wgPageName')) + '}} سیاهه]): ' : ': '; // localized
logText += 'پیشنهاد حذف'; // localized
if (params.logInitialContrib) {
logText += '; {{کاربر|' + params.logInitialContrib + '}} مطلع شد'; // localized
}
logText += ' ~~~~~\n';
logText += "#* '''دلیل''': " + params.reason + '\n'; // localized
summaryText = 'ثبت نامزدی [[:' + Morebits.pageNameNorm + ']] در سیاههٔ کاربری شما.'; // localized
usl.log(logText, summaryText + Twinkle.getPref('summaryAd'));
}
};
Twinkle.prod.callback.evaluate = function twinkleprodCallbackEvaluate(e) {
var form = e.target;
var params = {
usertalk: form.notify.checked,
reason: form.reason.value
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Twinkle.prod.callbacks.checkpriors({'params': params});
};
})(jQuery);
// </nowiki>
nfom1jdggyukv5d6s9ys7lblmzibeoh
کاربر:Mamouri/twinkle/twinkledeprod-2020.js
2
219493
909000
2026-07-30T16:04:57Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
909000
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** twinkledeprod.js: Batch deletion of expired PRODs (sysops only)
****************************************
* Mode of invocation: Tab ("Deprod")
* Active on: Categories whose name contains "proposed_deletion"
*/
Twinkle.deprod = function() {
if (
!Morebits.userIsSysop ||
mw.config.get('wgNamespaceNumber') !== 14 ||
!(/پیشنهاد.?حذف/i).test(mw.config.get('wgPageName')) // localized
) {
return;
}
Twinkle.addPortletLink(Twinkle.deprod.callback, 'حذف پیشنهادهای حذف دستهجمعی', 'tw-deprod', 'صفحههای ایجاد شده در این رده را حذف کنید'); // localized
};
var concerns = {};
Twinkle.deprod.callback = function() {
var Window = new Morebits.simpleWindow(800, 400);
Window.setTitle('انجام حذف پیشنهادهای حذف'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('پیشنهاد حذف', 'وو:پح'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#deprod'); // localized
var form = new Morebits.quickForm(callback_commit);
var statusdiv = document.createElement('div');
statusdiv.style.padding = '15px'; // just so it doesn't look broken
Window.setContent(statusdiv);
Morebits.status.init(statusdiv);
Window.display();
var query = {
'action': 'query',
'generator': 'categorymembers',
'gcmtitle': mw.config.get('wgPageName'),
'gcmlimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
'gcmnamespace': '0|6|108|2', // mostly to ignore categories
'prop': [ 'info', 'revisions' ],
'rvprop': [ 'content' ],
'inprop': [ 'protection' ]
};
var statelem = new Morebits.status('گرفتن فهرست صفحهها'); // localized
var wikipedia_api = new Morebits.wiki.api('در حال بارگیری...', query, function(apiobj) { // localized
var $doc = $(apiobj.responseXML);
var $pages = $doc.find('page[ns!="6"]'); // all non-files
var list = [];
var re = /\{\{\s*پیشنهاد حذف/; // localized
$pages.each(function() {
var $page = $(this);
var title = $page.attr('title');
var content = $page.find('revisions rev').text();
var $editprot = $page.find('pr[type="edit"][level="sysop"]');
var isProtected = $editprot.length > 0;
var metadata = [];
var res = re.exec(content);
if (res) {
var parsed = Morebits.wikitext.template.parse(content, res.index);
concerns[title] = parsed.parameters['1'] || parsed.parameters['اهمیت'] || ''; // localized
metadata.push(concerns[title]);
}
if (isProtected) {
metadata.push('محافظت کامل' + // localized
($editprot.attr('expiry') === 'infinity' ? ' بیپایان' : '، انقضا در ' + $editprot.attr('expiry'))); // localized
}
list.push({
label: metadata.length ? '(' + metadata.join('; ') + ')' : '',
value: title,
checked: concerns[title] !== '',
style: isProtected ? 'color:red' : ''
});
});
apiobj.params.form.append({ type: 'header', label: 'صفحهها برای حذف' }); // localized
apiobj.params.form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', true);
}
});
apiobj.params.form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', false);
}
});
apiobj.params.form.append({
'type': 'checkbox',
'name': 'pages',
'list': list
});
apiobj.params.form.append({
'type': 'submit'
});
var rendered = apiobj.params.form.render();
apiobj.params.Window.setContent(rendered);
$(Morebits.quickForm.getElements(rendered, 'pages')).each(function(index, checkbox) {
var $checkbox = $(checkbox);
var link = Morebits.htmlNode('a', $checkbox.val());
link.setAttribute('class', 'deprod-page-link');
link.setAttribute('href', mw.util.getUrl($checkbox.val()));
link.setAttribute('target', '_blank');
$checkbox.next().prepend([link, ' ']);
});
}, statelem);
wikipedia_api.params = { form: form, Window: Window };
wikipedia_api.post();
};
var callback_commit = function(event) {
var pages = event.target.getChecked('pages');
Morebits.status.init(event.target);
var batchOperation = new Morebits.batchOperation('حذف صفحهها'); // localized
batchOperation.setOption('chunkSize', Twinkle.getPref('proddeleteChunks'));
batchOperation.setOption('preserveIndividualStatusLines', true);
batchOperation.setPageList(pages);
batchOperation.run(function(pageName) {
var params = { page: pageName, reason: concerns[page] };
var query = {
'action': 'query',
'titles': pageName,
'prop': 'redirects',
'rdlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
var wikipedia_api = new Morebits.wiki.api('دریافت تغییرمسیرها', query, callback_deleteRedirects); // localized
wikipedia_api.params = params;
wikipedia_api.post();
query = {
'action': 'query',
'titles': 'بحث:' + pageName
};
wikipedia_api = new Morebits.wiki.api('بررسی این که ' + pageName + ' صفحهٔ بحث دارد یا نه', query, // localized
callback_deleteTalk);
wikipedia_api.params = params;
wikipedia_api.post();
var page = new Morebits.wiki.page(pageName, 'در حال حذف صفحه ' + pageName); // localized
page.setEditSummary('حذف طبق [[ویکیواژه:پیشنهاد حذف|پیشنهاد حذف]]، دلیل صفحه: ' + (concerns[pageName] === undefined ? 'نامشخص' : concerns[pageName]) + Twinkle.getPref('deletionSummaryAd')); // localized
page.suppressProtectWarning();
page.deletePage(batchOperation.workerSuccess, batchOperation.workerFailure);
});
},
callback_deleteTalk = function(apiobj) {
var $doc = $(apiobj.responseXML);
var exists = $doc.find('page:not([missing])').length > 0;
if (!exists) {
// no talk page; forget about it
return;
}
var page = new Morebits.wiki.page('Talk:' + apiobj.params.page, 'حذف صفحهٔ بحث ' + apiobj.params.page); // localized
page.setEditSummary('[[وو:محس#ع۸|ع۸]]: [[راهنما:صفحه بحث|صفحهٔ بحث]] صفحهٔ حذف شده «' + apiobj.params.page + '»' + Twinkle.getPref('deletionSummaryAd')); // localized
page.deletePage();
},
callback_deleteRedirects = function(apiobj) {
var $doc = $(apiobj.responseXML);
$doc.find('redirects rd').each(function() {
var title = $(this).attr('title');
var page = new Morebits.wiki.page(title, 'حذف تغییر مسیر صفحه ' + title); // localized
page.setEditSummary('[[وو:محس#ع۸|ع۸]]: تغییر مسیر به صفحهٔ حذف شده «' + apiobj.params.page + '»' + Twinkle.getPref('deletionSummaryAd')); // localized
page.deletePage();
});
};
})(jQuery);
// </nowiki>
8jdl2m6nrmtnd87sp78xvfsrgfw6dgq
کاربر:Mamouri/twinkle/twinkleprotect-2020.js
2
219494
909001
2026-07-30T16:05:04Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
909001
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** twinkleprotect.js: Protect/RPP module
****************************************
* Mode of invocation: Tab ("PP"/"RPP")
* Active on: Non-special, non-MediaWiki pages
*/
// Note: a lot of code in this module is re-used/called by batchprotect.
Twinkle.protect = function twinkleprotect() {
if (mw.config.get('wgNamespaceNumber') < 0 || mw.config.get('wgNamespaceNumber') === 8) {
return;
}
Twinkle.addPortletLink(Twinkle.protect.callback, Morebits.userIsSysop ? 'حفاظت' : 'حفاظت', 'tw-rpp', // localized
Morebits.userIsSysop ? 'حفاظت صفحه' : 'درخواست حفاظت صفحه'); // localized
};
Twinkle.protect.callback = function twinkleprotectCallback() {
var Window = new Morebits.simpleWindow(620, 530);
Window.setTitle(Morebits.userIsSysop ? 'اعمال، درخواست یا برچسبگذاری حفاظت صفحه' : 'درخواست یا برچسبگذاری حفاظت صفحه'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('الگوهای حفاظت', 'الگو:الگوهای حفاظت'); // localized
Window.addFooterLink('سیاست حفاظت', 'وو:حفاظت'); // localized
Window.addFooterLink('راهنمای توینکل', 'وو:تل/توضیحات#حفاظت'); // localized
var form = new Morebits.quickForm(Twinkle.protect.callback.evaluate);
var actionfield = form.append({
type: 'field',
label: 'نوع کنش' // localized
});
if (Morebits.userIsSysop) {
actionfield.append({
type: 'radio',
name: 'actiontype',
event: Twinkle.protect.callback.changeAction,
list: [
{
label: 'محافظت صفحه', // localized
value: 'protect',
tooltip: 'اجرای حفاظت بر روی صفحه.', // localized
checked: true
}
]
});
}
actionfield.append({
type: 'radio',
name: 'actiontype',
event: Twinkle.protect.callback.changeAction,
list: [
{
label: 'درخواست حفاظت صفحه', // localized
value: 'request',
tooltip: 'اگر می خواهید ' + (Morebits.userIsSysop ? ' بجای اینکه خودتان شخصا محافظت را اجرا کنید،' : 'از طریق وو:دمص درخواست دهید'), // localized
checked: !Morebits.userIsSysop
},
{
label: 'برچسبزدن صفحه با الگوی حفاظت', // localized
value: 'tag',
tooltip: 'اگر مدیر حفاظتکننده فراموش کرده باشد برچسب بزند یا خودتان بدون افزودن برچسب حفاظت را اجرا کردهاید، از اینجا میتوانید برچسب را اعمال کنید', // localized
disabled: mw.config.get('wgArticleId') === 0 || mw.config.get('wgPageContentModel') === 'Scribunto'
}
]
});
form.append({ type: 'field', label: 'پیشفرض', name: 'field_preset' });
form.append({ type: 'field', label: '1', name: 'field1' });
form.append({ type: 'field', label: '2', name: 'field2' });
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Window.display();
// We must init the controls
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.actiontype[0].dispatchEvent(evt);
// get current protection level asynchronously
Twinkle.protect.fetchProtectionLevel();
};
// Contains the current protection level in an object
// Once filled, it will look something like:
// { edit: { level: "sysop", expiry: <some date>, cascade: true }, ... }
Twinkle.protect.currentProtectionLevels = {};
// returns a jQuery Deferred object, usage:
// Twinkle.protect.fetchProtectingAdmin(apiObject, pageName, protect/stable).done(function(admin_username) { ...code... });
Twinkle.protect.fetchProtectingAdmin = function twinkleprotectFetchProtectingAdmin(api, pageName, protType, logIds) {
logIds = logIds || [];
return api.get({
format: 'json',
action: 'query',
list: 'logevents',
letitle: pageName,
letype: protType
}).then(function(data) {
// don't check log entries that have already been checked (e.g. don't go into an infinite loop!)
var event = data.query ? $.grep(data.query.logevents, function(le) {
return $.inArray(le.logid, logIds);
})[0] : null;
if (!event) {
// fail gracefully
return null;
} else if (event.action === 'move_prot' || event.action === 'move_stable') {
return twinkleprotectFetchProtectingAdmin(api, protType === 'protect' ? event.params.oldtitle_title : event.params.oldtitle, protType, logIds.concat(event.logid));
}
return event.user;
});
};
// mw.loader.getState('ext.flaggedRevs.review') returns null if the
// FlaggedRevs extension is not registered. Previously, this was done with
// wgFlaggedRevsParams, but after 1.34-wmf4 it is no longer exported if empty
// (https://gerrit.wikimedia.org/r/c/mediawiki/extensions/FlaggedRevs/+/508427)
Twinkle.protect.fetchProtectionLevel = function twinkleprotectFetchProtectionLevel() {
var api = new mw.Api();
var protectDeferred = api.get({
format: 'json',
indexpageids: true,
action: 'query',
list: 'logevents',
letype: 'protect',
letitle: mw.config.get('wgPageName'),
prop: mw.loader.getState('ext.flaggedRevs.review') ? 'info|flagged' : 'info',
inprop: 'protection',
titles: mw.config.get('wgPageName')
});
var stableDeferred = api.get({
format: 'json',
action: 'query',
list: 'logevents',
letype: 'stable',
letitle: mw.config.get('wgPageName')
});
var earlyDecision = [protectDeferred];
if (mw.loader.getState('ext.flaggedRevs.review')) {
earlyDecision.push(stableDeferred);
}
$.when.apply($, earlyDecision).done(function(protectData, stableData) {
// $.when.apply is supposed to take an unknown number of promises
// via an array, which it does, but the type of data returned varies.
// If there are two or more deferreds, it returns an array (of objects),
// but if there's just one deferred, it retuns a simple object.
// This is annoying.
protectData = $(protectData).toArray();
var pageid = protectData[0].query.pageids[0];
var page = protectData[0].query.pages[pageid];
var current = {}, adminEditDeferred;
$.each(page.protection, function(index, protection) {
if (protection.type !== 'aft') {
current[protection.type] = {
level: protection.level,
expiry: protection.expiry,
cascade: protection.cascade === ''
};
// logs report last admin who made changes to either edit/move/create protection, regardless if they only modified one of them
if (!adminEditDeferred) {
adminEditDeferred = Twinkle.protect.fetchProtectingAdmin(api, mw.config.get('wgPageName'), 'protect');
}
}
});
if (page.flagged) {
current.stabilize = {
level: page.flagged.protection_level,
expiry: page.flagged.protection_expiry
};
adminEditDeferred = Twinkle.protect.fetchProtectingAdmin(api, mw.config.get('wgPageName'), 'stable');
}
// show the protection level and log info
Twinkle.protect.hasProtectLog = !!protectData[0].query.logevents.length;
Twinkle.protect.hasStableLog = mw.loader.getState('ext.flaggedRevs.review') ? !!stableData[0].query.logevents.length : false;
Twinkle.protect.currentProtectionLevels = current;
if (adminEditDeferred) {
adminEditDeferred.done(function(admin) {
if (admin) {
$.each(['edit', 'move', 'create', 'stabilize'], function(i, type) {
if (Twinkle.protect.currentProtectionLevels[type]) {
Twinkle.protect.currentProtectionLevels[type].admin = admin;
}
});
}
Twinkle.protect.callback.showLogAndCurrentProtectInfo();
});
} else {
Twinkle.protect.callback.showLogAndCurrentProtectInfo();
}
});
};
Twinkle.protect.callback.showLogAndCurrentProtectInfo = function twinkleprotectCallbackShowLogAndCurrentProtectInfo() {
var currentlyProtected = !$.isEmptyObject(Twinkle.protect.currentProtectionLevels);
if (Twinkle.protect.hasProtectLog || Twinkle.protect.hasStableLog) {
var $linkMarkup = $('<span>');
if (Twinkle.protect.hasProtectLog) {
$linkMarkup.append(
$('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: mw.config.get('wgPageName'), type: 'protect'}) + '">protection log</a>'),
Twinkle.protect.hasStableLog ? $('<span> • </span>') : null
);
}
if (Twinkle.protect.hasStableLog) {
$linkMarkup.append($('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: mw.config.get('wgPageName'), type: 'stable'}) + '">pending changes log</a>)'));
}
Morebits.status.init($('div[name="hasprotectlog"] span')[0]);
Morebits.status.warn(
currentlyProtected ? 'محافظتهای پیشین' : 'این صفحه در گذشته مورد حفاظت قرار گرفتهاست', // localized
$linkMarkup[0]
);
}
Morebits.status.init($('div[name="currentprot"] span')[0]);
var protectionNode = [], statusLevel = 'info';
if (currentlyProtected) {
$.each(Twinkle.protect.currentProtectionLevels, function(type, settings) {
var label = type === 'stabilize' ? 'تغییرات در حال انتظار' : Morebits.string.toUpperCaseFirstChar(type); // localized
protectionNode.push($('<b>' + label + ': ' + settings.level + '</b>')[0]);
if (settings.expiry === 'infinity') {
protectionNode.push(' (بیپایان) '); // localized
} else {
protectionNode.push(' (زمان سرآمدن ' + new Date(settings.expiry).toUTCString() + ') '); // localized
}
if (settings.cascade) {
protectionNode.push('(آبشاری) '); // localized
}
if (settings.admin) {
var adminLink = '<a target="_blank" href="' + mw.util.getUrl('User talk:' + settings.admin) + '">' + settings.admin + '</a>';
protectionNode.push($('<span>توسط ' + adminLink + ' </span>')[0]); // localized
}
protectionNode.push($('<span> \u2022 </span>')[0]);
});
protectionNode = protectionNode.slice(0, -1); // remove the trailing bullet
statusLevel = 'warn';
} else {
protectionNode.push($('<b>محافظت نشده</b>')[0]); // localized
}
Morebits.status[statusLevel]('سطح محافظت فعلی', protectionNode); // localized
};
Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAction(e) {
var field_preset;
var field1;
var field2;
var isTemplate = mw.config.get('wgNamespaceNumber') === 10 || mw.config.get('wgNamespaceNumber') === 828;
switch (e.target.values) {
case 'protect':
field_preset = new Morebits.quickForm.element({ type: 'field', label: 'Preset', name: 'field_preset' });
field_preset.append({
type: 'select',
name: 'category',
label: 'انتخاب یک پیشفرض:', // localized
event: Twinkle.protect.callback.changePreset,
list: mw.config.get('wgArticleId') ?
Twinkle.protect.protectionTypes.filter(function(v) {
return isTemplate || v.label !== 'Template protection';
}) :
Twinkle.protect.protectionTypesCreate
});
field2 = new Morebits.quickForm.element({ type: 'field', label: 'گزینههای حفاظت', name: 'field2' }); // localized
field2.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback
field2.append({ type: 'div', name: 'hasprotectlog', label: ' ' });
// for existing pages
if (mw.config.get('wgArticleId')) {
field2.append({
type: 'checkbox',
name: 'editmodify',
event: Twinkle.protect.formevents.editmodify,
list: [
{
label: 'تغییر حفاظت در برابر ویرایش', // localized
value: 'editmodify',
tooltip: 'درصورت خاموشکردن این جعبه، سطح حفاظت از ویرایش و زمان سرآمدن به همان شکلی که هستند باقی میمانند', // localized
checked: true
}
]
});
var editlevel = field2.append({
type: 'select',
name: 'editlevel',
label: 'محافظت در برابر ویرایش:', // localized
event: Twinkle.protect.formevents.editlevel
});
editlevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
editlevel.append({
type: 'option',
label: 'تأییدشدهٔ خودکار', // localized
value: 'autoconfirmed'
});
editlevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed'
});
if (isTemplate) {
editlevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
}
editlevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
field2.append({
type: 'select',
name: 'editexpiry',
label: 'سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
// default expiry selection is conditionally set in Twinkle.protect.callback.changePreset
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'دلخواه', value: 'custom' } // localized
]
});
field2.append({
type: 'checkbox',
name: 'movemodify',
event: Twinkle.protect.formevents.movemodify,
list: [
{
label: 'تغییر حفاظت از انتقال', // localized
value: 'movemodify',
tooltip: 'اگر این جعبه خاموش باشد، سطح حفاظت از انتقال و زمان سرآمدن به همان شکلی که هستند باقیمیمانند', // localized
checked: true
}
]
});
var movelevel = field2.append({
type: 'select',
name: 'movelevel',
label: 'حفاظت دربرابر انتقال:', // localized
event: Twinkle.protect.formevents.movelevel
});
movelevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
movelevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed'
});
if (isTemplate) {
movelevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
}
movelevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
field2.append({
type: 'select',
name: 'moveexpiry',
label: 'سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
// default expiry selection is conditionally set in Twinkle.protect.callback.changePreset
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'دلخواه', value: 'custom' } // localized
]
});
// Namespaces hardcoded until [[phab:T218479]]
if (mw.loader.getState('ext.flaggedRevs.review') && (mw.config.get('wgNamespaceNumber') === 0 || mw.config.get('wgNamespaceNumber') === 4)) {
field2.append({
type: 'checkbox',
name: 'pcmodify',
event: Twinkle.protect.formevents.pcmodify,
list: [
{
label: 'تغییر حفاظت نیازمند بازبینی', // localized
value: 'pcmodify',
tooltip: 'اگر این جعبه خاموش باشد، سطح حفاظت نیازمند بازبینی و زمان سرآمدن به همان شکلی که هستند باقی میمانند', // localized
checked: true
}
]
});
var pclevel = field2.append({
type: 'select',
name: 'pclevel',
label: 'تغییرات درحال انتظار:', // localized
event: Twinkle.protect.formevents.pclevel
});
pclevel.append({
type: 'option',
label: 'هیچ', // localized
value: 'none'
});
pclevel.append({
type: 'option',
label: 'تغییرات در حال انتظار', // localized
value: 'autoconfirmed',
selected: true
});
field2.append({
type: 'select',
name: 'pcexpiry',
label: 'سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', selected: true, value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'دلخواه', value: 'custom' } // localized
]
});
}
} else { // for non-existing pages
var createlevel = field2.append({
type: 'select',
name: 'createlevel',
label: 'حفاظت در برابر ایجاد:', // localized
event: Twinkle.protect.formevents.createlevel
});
createlevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
if (mw.config.get('wgNamespaceNumber') !== 0) {
createlevel.append({
type: 'option',
label: 'تأیید خودکار', // localized
value: 'autoconfirmed'
});
}
if (isTemplate) {
createlevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
}
createlevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed',
selected: true
});
createlevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop'
});
field2.append({
type: 'select',
name: 'createexpiry',
label: 'سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', selected: true, value: 'indefinite' }, // localized
{ label: 'دلخواه', value: 'custom' } // localized
]
});
}
field2.append({
type: 'textarea',
name: 'protectReason',
label: 'دلیل (برای سیاههٔ محافظت):' // localized
});
if (!mw.config.get('wgArticleId') || mw.config.get('wgPageContentModel') === 'Scribunto') { // tagging isn't relevant for non-existing or module pages
break;
}
/* falls through */
case 'tag':
field1 = new Morebits.quickForm.element({ type: 'field', label: 'گزینههای برچسب زدن', name: 'field1' });
field1.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback
field1.append({ type: 'div', name: 'hasprotectlog', label: ' ' });
field1.append({
type: 'select',
name: 'tagtype',
label: 'انتخاب الگوی محافظت:', // localized
list: Twinkle.protect.protectionTags,
event: Twinkle.protect.formevents.tagtype
});
field1.append({
type: 'checkbox',
list: [
{
name: 'small',
label: 'کوچک (small=yes)', // localized
tooltip: 'استفاده از ویژگی |small=yes الگو، به شکلی که فقط قفل نمایش داده شود', // localized
checked: true
},
{
name: 'noinclude',
label: 'قراردادن الگوی حفاظت درون برچسب <noinclude>', // localized
tooltip: 'الگوی حفاظت را درون <noinclude> قرار میدهد تا تراگنجانش نشود', // localized
checked: mw.config.get('wgNamespaceNumber') === 10
}
]
});
break;
case 'request':
field_preset = new Morebits.quickForm.element({ type: 'field', label: 'نوع محافظت', name: 'field_preset' }); // localized
field_preset.append({
type: 'select',
name: 'category',
label: 'نوع و دلیل:', // localized
event: Twinkle.protect.callback.changePreset,
list: mw.config.get('wgArticleId') ? Twinkle.protect.protectionTypes : Twinkle.protect.protectionTypesCreate
});
field1 = new Morebits.quickForm.element({ type: 'field', label: 'گزینهها', name: 'field1' }); // localized
field1.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback
field1.append({ type: 'div', name: 'hasprotectlog', label: ' ' });
field1.append({
type: 'select',
name: 'expiry',
label: 'مدت: ', // localized
list: [
{ label: 'موقت', value: 'temporary' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: '', selected: true, value: '' }
]
});
field1.append({
type: 'textarea',
name: 'reason',
label: 'دلیل: ' // localized
});
break;
default:
alert("مشکلی در حین اجرای حفاظت توینکلی پیش آمد"); // localized
break;
}
var oldfield;
if (field_preset) {
oldfield = $(e.target.form).find('fieldset[name="field_preset"]')[0];
oldfield.parentNode.replaceChild(field_preset.render(), oldfield);
} else {
$(e.target.form).find('fieldset[name="field_preset"]').css('display', 'none');
}
if (field1) {
oldfield = $(e.target.form).find('fieldset[name="field1"]')[0];
oldfield.parentNode.replaceChild(field1.render(), oldfield);
} else {
$(e.target.form).find('fieldset[name="field1"]').css('display', 'none');
}
if (field2) {
oldfield = $(e.target.form).find('fieldset[name="field2"]')[0];
oldfield.parentNode.replaceChild(field2.render(), oldfield);
} else {
$(e.target.form).find('fieldset[name="field2"]').css('display', 'none');
}
if (e.target.values === 'protect') {
// fake a change event on the preset dropdown
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
e.target.form.category.dispatchEvent(evt);
// reduce vertical height of dialog
$(e.target.form).find('fieldset[name="field2"] select').parent().css({ display: 'inline-block', marginRight: '0.5em' });
}
// re-add protection level and log info, if it's available
Twinkle.protect.callback.showLogAndCurrentProtectInfo();
};
Twinkle.protect.formevents = {
editmodify: function twinkleprotectFormEditmodifyEvent(e) {
e.target.form.editlevel.disabled = !e.target.checked;
e.target.form.editexpiry.disabled = !e.target.checked || (e.target.form.editlevel.value === 'all');
e.target.form.editlevel.style.color = e.target.form.editexpiry.style.color = e.target.checked ? '' : 'transparent';
},
editlevel: function twinkleprotectFormEditlevelEvent(e) {
e.target.form.editexpiry.disabled = e.target.value === 'all';
},
movemodify: function twinkleprotectFormMovemodifyEvent(e) {
// sync move settings with edit settings if applicable
if (e.target.form.movelevel.disabled && !e.target.form.editlevel.disabled) {
e.target.form.movelevel.value = e.target.form.editlevel.value;
e.target.form.moveexpiry.value = e.target.form.editexpiry.value;
} else if (e.target.form.editlevel.disabled) {
e.target.form.movelevel.value = 'sysop';
e.target.form.moveexpiry.value = 'indefinite';
}
e.target.form.movelevel.disabled = !e.target.checked;
e.target.form.moveexpiry.disabled = !e.target.checked || (e.target.form.movelevel.value === 'all');
e.target.form.movelevel.style.color = e.target.form.moveexpiry.style.color = e.target.checked ? '' : 'transparent';
},
movelevel: function twinkleprotectFormMovelevelEvent(e) {
e.target.form.moveexpiry.disabled = e.target.value === 'all';
},
pcmodify: function twinkleprotectFormPcmodifyEvent(e) {
e.target.form.pclevel.disabled = !e.target.checked;
e.target.form.pcexpiry.disabled = !e.target.checked || (e.target.form.pclevel.value === 'none');
e.target.form.pclevel.style.color = e.target.form.pcexpiry.style.color = e.target.checked ? '' : 'transparent';
},
pclevel: function twinkleprotectFormPclevelEvent(e) {
e.target.form.pcexpiry.disabled = e.target.value === 'none';
},
createlevel: function twinkleprotectFormCreatelevelEvent(e) {
e.target.form.createexpiry.disabled = e.target.value === 'all';
},
tagtype: function twinkleprotectFormTagtypeEvent(e) {
e.target.form.small.disabled = e.target.form.noinclude.disabled = (e.target.value === 'none') || (e.target.value === 'noop');
}
};
Twinkle.protect.doCustomExpiry = function twinkleprotectDoCustomExpiry(target) {
var custom = prompt('یک زمان سرآمدن سفارشی وارد کنید. \n میتوانید زمانهای نسبی مثل «۱ دقیقه» یا «۱۹ روز» یا زمانمهر مطلق به شکل yyyymmddhhmm وارد کنید (به عنوان مثال "200602011405" نشان دهندهٔ اول فوریهٔ ۲۰۰۶، ساعت ۱۴:۰۵ جهانی است).', ''); // localized fixes might be necessary
if (custom) {
var option = document.createElement('option');
option.setAttribute('value', custom);
option.textContent = custom;
target.appendChild(option);
target.value = custom;
} else {
target.selectedIndex = 0;
}
};
Twinkle.protect.protectionTypes = [
{ label: 'خروج از حفاظت', value: 'unprotect' }, // localized
{
label: 'حفاظت کامل', // localized
list: [
{ label: 'کلی (کامل)', value: 'pp-protected' }, // localized
{ label: 'اختلاف نظر / جنگ ویرایشی (کامل)', value: 'pp-dispute' }, // localized
{ label: 'خرابکاری مداوم (کامل)', value: 'pp-vandalism' }, // localized
{ label: 'بحث کاربری بستهشده (کامل)', value: 'pp-usertalk' } // localized
]
},
{
label: 'حفاظت الگو', // localized
list: [
{ label: 'الگوی پرکاربرد', value: 'pp-template' } // localized
]
},
{
label: 'Extended confirmed protection',
list: [
{ label: 'تصمیم هیئت داوری', selected: true, value: 'pp-30-500-arb' }, // localized
{ label: 'خرابکاری پیدرپی', value: 'pp-30-500-vandalism' }, // localized
{ label: 'ویرایش فلجکننده', value: 'pp-30-500-disruptive' }, // localized
{ label: 'سرپیچی از سیاست زندگینامهٔ زندگان', value: 'pp-30-500-blp' }, // localized
{ label: 'زاپاسبازی', value: 'pp-30-500-sock' } // localized
]
},
{
label: 'Semi-protection',
list: [
{ label: 'کلی (نیمهحفاظت)', value: 'pp-semi-protected' }, // localized
{ label: 'خرابکاری ادامهدار (نیمهحفاظت)', selected: true, value: 'pp-semi-vandalism' }, // localized
{ label: 'ویرایش فلجکننده (نیمهحفاظت)', value: 'pp-semi-disruptive' }, // localized
{ label: 'افزودن مطالب بدون منبع (نیمه حفاظت)', value: 'pp-semi-unsourced' }, // localized
{ label: 'سرپیچی از سیاست زندگینامهٔ زندگان (نیمهحفاظت)', value: 'pp-semi-blp' }, // localized
{ label: 'زاپاسبازی (نیمهحفاظت)', value: 'pp-semi-sock' }, // localized
{ label: 'صفحهٔ بحث کاربر بستهشده (نیمهحفاظت)', value: 'pp-semi-usertalk' } // localized
]
},
{
label: 'تغییرات در حال انتظار', // localized
list: [
{ label: 'کلی', value: 'pp-pc-protected' }, // localized
{ label: 'خرابکاری پیدرپی', value: 'pp-pc-vandalism' }, // localized
{ label: 'ویرایش فلجکننده', value: 'pp-pc-disruptive' }, // localized
{ label: 'افزودن مطالب بیمنبع', value: 'pp-pc-unsourced' }, // localized
{ label: 'سرپیچی از سیاست زندگینامهٔ زندگان', value: 'pp-pc-blp' } // localized
]
},
{
label: 'حفاظت از انتقال', // localized
list: [
{ label: 'کلی (انتقال)', value: 'pp-move' }, // localized
{ label: 'اختلاف نظر/جنگ برسر عنوان (انتقال)', value: 'pp-move-dispute' }, // localized
{ label: 'خرابکاری از طریق انتقال (انتقال)', value: 'pp-move-vandalism' }, // localized
{ label: 'صفحهٔ پربازدید (انتقال)', value: 'pp-move-indef' } // localized
]
}
];
Twinkle.protect.protectionTypesCreate = [
{ label: 'خروج از محافظت', value: 'unprotect' }, // localized
{
label: 'محافظت در برابر ایجاد', // localized
list: [
{ label: 'کلی ({{pp-create}})', value: 'pp-create' }, // localized
{ label: 'نام نامناسب', value: 'pp-create-offensive' }, // localized
{ label: 'ایجادهای مجدد', selected: true, value: 'pp-create-salt' }, // localized
{ label: 'زندگینامهٔ زندگان که یه تازگی حذفشده', value: 'pp-create-blp' } // localized
]
}
];
// A page with both regular and PC protection will be assigned its regular
// protection weight plus 2
Twinkle.protect.protectionWeight = {
sysop: 40,
templateeditor: 30,
extendedconfirmed: 20,
autoconfirmed: 10,
flaggedrevs_autoconfirmed: 5, // Pending Changes protection alone
all: 0,
flaggedrevs_none: 0 // just in case
};
// NOTICE: keep this synched with [[MediaWiki:Protect-dropdown]]
// Also note: stabilize = Pending Changes level
Twinkle.protect.protectionPresetsInfo = {
'pp-protected': {
edit: 'sysop',
move: 'sysop',
reason: null
},
'pp-dispute': {
edit: 'sysop',
move: 'sysop',
reason: '[[وو:مناقشه|مناقشه بر سر محتوا/جنگ ویرایشی]]' // localized
},
'pp-vandalism': {
edit: 'sysop',
move: 'sysop',
reason: '[[وو:خ|خرابکاری]] ادامهدار' // localized
},
'pp-usertalk': {
edit: 'sysop',
move: 'sysop',
reason: '[[وو:حبک|استفادهٔ نادرست از صفحهٔ بحث کاربر در حین بندایش]]' // localized
},
'pp-template': {
edit: 'templateeditor',
move: 'templateeditor',
reason: '[[وو:حساس|الگوهای بسیار پرکاربرد]]' // localized
},
'pp-30-500-arb': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: '[[وو:قفل آبی|درخواست هیئت نظارت]]',
template: 'pp-30-500'
},
'pp-30-500-vandalism': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: ' [[وو:خ|خرابکاری]] ادامهدار از سوی حسابهای تأییدشده(یخودکار)', // localized
template: 'pp-30-500'
},
'pp-30-500-disruptive': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: '[[وو:اخلال|ویرایش اخلالگرانهٔ]] ادامهدار توسط کاربران تأیید شده', // localized
template: 'pp-30-500'
},
'pp-30-500-blp': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: 'نقض ادامهدار [[وو:زز|سیاست زندگینامه زندگان]] توسط کاربران تأیید شده', // localized
template: 'pp-30-500'
},
'pp-30-500-sock': {
edit: 'extendedconfirmed',
move: 'extendedconfirmed',
reason: 'Persistent [[وو:زاپاس|زاپاس بازی]]', // localized
template: 'pp-30-500'
},
'pp-semi-vandalism': {
edit: 'autoconfirmed',
reason: '[[وو:خرابکاری|خرابکاری]] ادامهدار', // localized
template: 'pp-vandalism'
},
'pp-semi-disruptive': {
edit: 'autoconfirmed',
reason: '[[وو:اخلال|ویرایش اخلالگرانهٔ]] ادامهدار', // localized
template: 'pp-protected'
},
'pp-semi-unsourced': {
edit: 'autoconfirmed',
reason: 'افزودن مداوم [[WP:INTREF|مطالب بدون منبع یا با منبع نامعتبر]]', // localized
template: 'pp-protected'
},
'pp-semi-blp': {
edit: 'autoconfirmed',
reason: 'Violations of the [[WP:BLP|biographies of living persons policy]]',
template: 'pp-blp'
},
'pp-semi-usertalk': {
edit: 'autoconfirmed',
move: 'autoconfirmed',
reason: '[[WP:PP#Talk-page protection|Inappropriate use of user talk page while blocked]]',
template: 'pp-usertalk'
},
'pp-semi-template': { // removed for now
edit: 'autoconfirmed',
move: 'autoconfirmed',
reason: '[[WP:High-risk templates|Highly visible template]]',
template: 'pp-template'
},
'pp-semi-sock': {
edit: 'autoconfirmed',
reason: 'Persistent [[WP:Sock puppetry|sock puppetry]]',
template: 'pp-sock'
},
'pp-semi-protected': {
edit: 'autoconfirmed',
reason: null,
template: 'pp-protected'
},
'pp-pc-vandalism': {
stabilize: 'autoconfirmed', // stabilize = Pending Changes
reason: '[[وو:خرابکاری|خرابکاری]] مداوم', // localized
template: 'pp-pc'
},
'pp-pc-disruptive': {
stabilize: 'autoconfirmed',
reason: '[[وو:اخلال|ویرایشهای اخلالگرانه]] ادامهدار', // localized
template: 'pp-pc'
},
'pp-pc-unsourced': {
stabilize: 'autoconfirmed',
reason: 'ادامهدار بودن [[WP:INTREF|افزودن مداوم مطالب بدون منبع یا منبع ضعیف]]', // localized
template: 'pp-pc'
},
'pp-pc-blp': {
stabilize: 'autoconfirmed',
reason: 'نقض [[وو:زنده|سیاست زندگینامه زندگان]]', // localized
template: 'pp-pc'
},
'pp-pc-protected': {
stabilize: 'autoconfirmed',
reason: null,
template: 'pp-pc'
},
'pp-move': {
move: 'sysop',
reason: null
},
'pp-move-dispute': {
move: 'sysop',
reason: '[[وو:قفل سبز|مناقشه بر سر نام]]' // localized
},
'pp-move-vandalism': {
move: 'sysop',
reason: '[[وو:قفل سبز|خرابکاری از طریق انتقال]]' // localized
},
'pp-move-indef': {
move: 'sysop',
reason: '[[وو:قفل سبز|صفحهٔ پربیننده]]' // localized
},
'unprotect': {
edit: 'all',
move: 'all',
stabilize: 'none',
create: 'all',
reason: null,
template: 'none'
},
'pp-create-offensive': {
create: 'sysop',
reason: '[[وو:نمک|نام نامناسب]]' // localized
},
'pp-create-salt': {
create: 'extendedconfirmed',
reason: '[[وو:نمک|ایجاد پیاپی]]' // localized
},
'pp-create-blp': {
create: 'extendedconfirmed',
reason: '[[WP:BLPDEL|Recently deleted BLP]]'
},
'pp-create': {
create: 'extendedconfirmed',
reason: '{{pp-create}}' // localized version to be decided, non-existing in source
}
};
Twinkle.protect.protectionTags = [
{
label: 'هیچ (حذف الگوهای حفاظت فعلی)', // localized
value: 'none'
},
{
label: 'هیچ (بدون حذف الگوهای حفاظت فعلی)', // localized
value: 'noop'
},
{
label: 'الگوهای حفاظت دربرابر ویرایش', // localized
list: [
{ label: '{{pp-vandalism}}: خرابکاری', value: 'pp-vandalism' }, // localized
{ label: '{{pp-dispute}}: مناقشه/جنگ ویرایشی', value: 'pp-dispute' }, // localized
{ label: '{{pp-blp}}: سرپیچی از سیاست زندگینامهٔ زندگان', value: 'pp-blp' }, // localized
{ label: '{{pp-sock}}: زاپاسبازی', value: 'pp-sock' }, // localized
{ label: '{{pp-template}}: الگوی حساس', value: 'pp-template' }, // localized
{ label: '{{pp-usertalk}}: صفحهٔ بحث کاربر بستهشده', value: 'pp-usertalk' }, // localized
{ label: '{{pp-protected}}: حفاظت کلی', value: 'pp-protected' }, // localized
{ label: '{{pp-semi-indef}}: نیمهحفاظت کلی و طولانیمدت', value: 'pp-semi-indef' }, // localized
{ label: '{{pp-30-500}}: نیمهحفاظت ویژه', value: 'pp-30-500' } // localized
]
},
{
label: 'الگوهای تغییرات درحال انتظار', // localized
list: [
{ label: '{{pp-pc}}: تغییرات در حال انتظار', value: 'pp-pc' } // localized
]
},
{
label: 'الگوهای حفاظت در برابر انتقال', // localized
list: [
{ label: '{{pp-move-dispute}}: مناقشه/جنگ بر سر انتقال', value: 'pp-move-dispute' }, // localized
{ label: '{{pp-move-vandalism}}: خرابکاری از طریق انتقال', value: 'pp-move-vandalism' }, // localized
{ label: '{{pp-move-indef}}: کلی و طولانیمدت', value: 'pp-move-indef' }, // localized
{ label: '{{pp-move}}: سایر', value: 'pp-move' } // localized
]
}
];
Twinkle.protect.callback.changePreset = function twinkleprotectCallbackChangePreset(e) {
var form = e.target.form;
var actiontypes = form.actiontype;
var actiontype;
for (var i = 0; i < actiontypes.length; i++) {
if (!actiontypes[i].checked) {
continue;
}
actiontype = actiontypes[i].values;
break;
}
if (actiontype === 'protect') { // actually protecting the page
var item = Twinkle.protect.protectionPresetsInfo[form.category.value];
if (mw.config.get('wgArticleId')) {
if (item.edit) {
form.editmodify.checked = true;
Twinkle.protect.formevents.editmodify({ target: form.editmodify });
form.editlevel.value = item.edit;
Twinkle.protect.formevents.editlevel({ target: form.editlevel });
form.editexpiry.value = '2 days';
} else {
form.editmodify.checked = false;
Twinkle.protect.formevents.editmodify({ target: form.editmodify });
}
if (item.move) {
form.movemodify.checked = true;
Twinkle.protect.formevents.movemodify({ target: form.movemodify });
form.movelevel.value = item.move;
Twinkle.protect.formevents.movelevel({ target: form.movelevel });
form.moveexpiry.value = '2 days';
} else {
form.movemodify.checked = false;
Twinkle.protect.formevents.movemodify({ target: form.movemodify });
}
if (form.pcmodify) {
if (item.stabilize) {
form.pcmodify.checked = true;
Twinkle.protect.formevents.pcmodify({ target: form.pcmodify });
form.pclevel.value = item.stabilize;
Twinkle.protect.formevents.pclevel({ target: form.pclevel });
} else {
form.pcmodify.checked = false;
Twinkle.protect.formevents.pcmodify({ target: form.pcmodify });
}
}
} else {
if (item.create) {
form.createlevel.value = item.create;
Twinkle.protect.formevents.createlevel({ target: form.createlevel });
}
}
var reasonField = actiontype === 'protect' ? form.protectReason : form.reason;
if (item.reason) {
reasonField.value = item.reason;
} else {
reasonField.value = '';
}
// sort out tagging options, disabled if nonexistent or lua
if (mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') !== 'Scribunto') {
if (form.category.value === 'unprotect') {
form.tagtype.value = 'none';
} else {
form.tagtype.value = item.template ? item.template : form.category.value;
}
Twinkle.protect.formevents.tagtype({ target: form.tagtype });
if (/template/.test(form.category.value)) {
form.noinclude.checked = true;
form.editexpiry.value = form.moveexpiry.value = form.pcexpiry.value = 'indefinite';
} else if (mw.config.get('wgNamespaceNumber') !== 10) {
form.noinclude.checked = false;
}
}
} else { // RPP request
if (form.category.value === 'unprotect') {
form.expiry.value = '';
form.expiry.disabled = true;
} else {
form.expiry.value = '';
form.expiry.disabled = false;
}
}
};
Twinkle.protect.callback.evaluate = function twinkleprotectCallbackEvaluate(e) {
var form = e.target;
var actiontypes = form.actiontype;
var actiontype;
for (var i = 0; i < actiontypes.length; i++) {
if (!actiontypes[i].checked) {
continue;
}
actiontype = actiontypes[i].values;
break;
}
var tagparams;
if (actiontype === 'tag' || (actiontype === 'protect' && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') !== 'Scribunto')) {
tagparams = {
tag: form.tagtype.value,
reason: (form.tagtype.value === 'pp-protected' || form.tagtype.value === 'pp-semi-protected' || form.tagtype.value === 'pp-move') && form.protectReason ? form.protectReason.value : null,
small: form.small.checked,
noinclude: form.noinclude.checked
};
}
switch (actiontype) {
case 'protect':
// protect the page
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'محافظت کامل شد'; // localized
var statusInited = false;
var thispage;
var allDone = function twinkleprotectCallbackAllDone() {
if (thispage) {
thispage.getStatusElement().info('done');
}
if (tagparams) {
Twinkle.protect.callbacks.taggingPageInitial(tagparams);
}
};
var stabilizeValues = {};
if (form.pclevel) {
stabilizeValues = {
pclevel: form.pclevel.value,
pcexpiry: form.pcexpiry.value,
protectReason: form.protectReason.value
};
}
var protectIt = function twinkleprotectCallbackProtectIt(next) {
thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), ' محافظت صفحه'); // localized
if (mw.config.get('wgArticleId')) {
if (form.editmodify.checked) {
thispage.setEditProtection(form.editlevel.value, form.editexpiry.value);
}
if (form.movemodify.checked) {
thispage.setMoveProtection(form.movelevel.value, form.moveexpiry.value);
}
} else {
thispage.setCreateProtection(form.createlevel.value, form.createexpiry.value);
thispage.setWatchlist(false);
}
if (form.protectReason.value) {
thispage.setEditSummary(form.protectReason.value);
} else {
alert('میبایست دلیلی برای حفاظت جهت ذکر در سیاههٔ محافظت ارائه دهید.'); // localized
return;
}
if (!statusInited) {
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
statusInited = true;
}
thispage.protect(next);
};
var stabilizeIt = function twinkleprotectCallbackStabilizeIt() {
if (thispage) {
thispage.getStatusElement().info('done');
}
thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Applying pending changes protection');
thispage.setFlaggedRevs(stabilizeValues.pclevel, stabilizeValues.pcexpiry);
if (stabilizeValues.protectReason) {
thispage.setEditSummary(stabilizeValues.protectReason);
} else {
alert('باید دلیلی برای درخواست ذکر کنید تا در سیاهه محافظت ثبت شود.'); // localized
return;
}
if (!statusInited) {
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
statusInited = true;
}
thispage.stabilize(allDone, function(error) {
if (error.errorCode === 'stabilize_denied') { // [[phab:T234743]]
thispage.getStatusElement().error('Failed trying to modify pending changes settings, likely due to a mediawiki bug. Other actions (tagging or regular protection) may have taken place. Please reload the page and try again.');
}
});
};
if ((form.editmodify && form.editmodify.checked) || (form.movemodify && form.movemodify.checked) ||
!mw.config.get('wgArticleId')) {
if (form.pcmodify && form.pcmodify.checked) {
protectIt(stabilizeIt);
} else {
protectIt(allDone);
}
} else if (form.pcmodify && form.pcmodify.checked) {
stabilizeIt();
} else {
alert("لطفا دستوری به توینکل دهید.\nاگر صرفا قصد برچسبزدن دارید، گزینهٔ 'برچسب زدن صفحه' را از بالاانتخاب کنید."); // localized
}
break;
case 'tag':
// apply a protection template
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.followRedirect = false;
Morebits.wiki.actionCompleted.notice = 'برچسب زدن کامل شد'; // localized
Twinkle.protect.callbacks.taggingPageInitial(tagparams);
break;
case 'request':
// file request at RFPP
var typename, typereason;
switch (form.category.value) {
case 'pp-dispute':
case 'pp-vandalism':
case 'pp-usertalk':
case 'pp-protected':
typename = 'full protection';
break;
case 'pp-template':
typename = 'template protection';
break;
case 'pp-30-500-arb':
case 'pp-30-500-vandalism':
case 'pp-30-500-disruptive':
case 'pp-30-500-blp':
case 'pp-30-500-sock':
typename = 'extended confirmed protection';
break;
case 'pp-semi-vandalism':
case 'pp-semi-disruptive':
case 'pp-semi-unsourced':
case 'pp-semi-usertalk':
case 'pp-semi-sock':
case 'pp-semi-blp':
case 'pp-semi-protected':
typename = 'semi-protection';
break;
case 'pp-pc-vandalism':
case 'pp-pc-blp':
case 'pp-pc-protected':
case 'pp-pc-unsourced':
case 'pp-pc-disruptive':
typename = 'pending changes';
break;
case 'pp-move':
case 'pp-move-dispute':
case 'pp-move-indef':
case 'pp-move-vandalism':
typename = 'move protection';
break;
case 'pp-create':
case 'pp-create-offensive':
case 'pp-create-blp':
case 'pp-create-salt':
typename = 'create protection';
break;
case 'unprotect':
var admins = $.map(Twinkle.protect.currentProtectionLevels, function(pl) {
return pl.admin ? 'User:' + pl.admin : null;
});
if (admins.length && !confirm('آیا قبلا با مدیران حفاظت کننده (' + $.unique(admins).join(', ') + ') تماس گرفتهاید؟')) { // localized
return false;
}
// otherwise falls through
default:
typename = 'unprotection';
break;
}
switch (form.category.value) {
case 'pp-dispute':
typereason = 'مناقشه بر سر محتوا/جنگ ویرایشی'; // localized
break;
case 'pp-vandalism':
case 'pp-semi-vandalism':
case 'pp-pc-vandalism':
case 'pp-30-500-vandalism':
typereason = ' [[وو:خ|خرابکاری]] ادامهدار'; // localized
break;
case 'pp-semi-disruptive':
case 'pp-pc-disruptive':
case 'pp-30-500-disruptive':
typereason = ' [[Wikipedia:Disruptive editing|ویرایش فلجکنندهٔ ]] ادامهدار'; // localized
break;
case 'pp-semi-unsourced':
case 'pp-pc-unsourced':
typereason = '[[WP:INTREF|مطالب بدون منبع]] افزودن ادامهدار'; // localized
break;
case 'pp-template':
typereason = '[[WP:HIGHRISK|الگوی حساس]]'; // localized
break;
case 'pp-30-500-arb':
typereason = '[[WP:30/500|تصمیم هیئت داوری]]'; // localized
break;
case 'pp-usertalk':
case 'pp-semi-usertalk':
typereason = 'استفادهٔ نادرست از صفحهٔ بحث کاربر در حین بندایش'; // localized
break;
case 'pp-semi-sock':
case 'pp-30-500-sock':
typereason = '[[وو:ز|زاپاسبازی]] ادامهدار'; // localized
break;
case 'pp-semi-blp':
case 'pp-pc-blp':
case 'pp-30-500-blp':
typereason = 'سرپیچی از سیاست زندگینامهٔ زندگان'; // localized
break;
case 'pp-move-dispute':
typereason = 'مناقشه/جنگ بر سر عنوان/انتقال'; // localized
break;
case 'pp-move-vandalism':
typereason = 'خرابکاری از طریق اناقال صفحات'; // localized
break;
case 'pp-move-indef':
typereason = 'صفحهٔ پربازدید'; // localized
break;
case 'pp-create-offensive':
typereason = 'نام نامناسب'; // localized
break;
case 'pp-create-blp':
typereason = 'زندگینامهٔ زندگان که به تازگی حذف شده'; // localized
break;
case 'pp-create-salt':
typereason = 'ایجادهای پیدرپی'; // localized
break;
default:
typereason = '';
break;
}
var reason = typereason;
if (form.reason.value !== '') {
if (typereason !== '') {
reason += '\u00A0\u2013 '; // U+00A0 NO-BREAK SPACE; U+2013 EN RULE
}
reason += form.reason.value;
}
if (reason !== '' && reason.charAt(reason.length - 1) !== '.') {
reason += '.';
}
var rppparams = {
reason: reason,
typename: typename,
category: form.category.value,
expiry: form.expiry.value
};
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
var rppName = 'ویکیواژه:درخواست محافظت از صفحه'; // localized
// Updating data for the action completed event
Morebits.wiki.actionCompleted.redirect = rppName;
Morebits.wiki.actionCompleted.notice = 'درخواست کامل شد، درحال بازگشت به صفحه بحث'; // localized
var rppPage = new Morebits.wiki.page(rppName, 'درحال درخواست برای محافظت از صفحه'); // localized
rppPage.setFollowRedirect(true);
rppPage.setCallbackParameters(rppparams);
rppPage.load(Twinkle.protect.callbacks.fileRequest);
break;
default:
alert('twinkleprotect: unknown kind of action');
break;
}
};
Twinkle.protect.callbacks = {
taggingPageInitial: function(tagparams) {
if (tagparams.tag === 'noop') {
Morebits.status.info(' اعمال الگوی حفاظت', 'کاری برای انجام نیست'); // localized
return;
}
var protectedPage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'برچسب زدن صفحه'); // localized
protectedPage.setCallbackParameters(tagparams);
protectedPage.load(Twinkle.protect.callbacks.taggingPage);
},
taggingPage: function(protectedPage) {
var params = protectedPage.getCallbackParameters();
var text = protectedPage.getPageText();
var tag, summary;
var oldtag_re = /\s*(?:<noinclude>)?\s*\{\{\s*(pp-[^{}]*?|protected|(?:t|v|s|p-|usertalk-v|usertalk-s|sb|move)protected(?:2)?|protected template|privacy protection)\s*?\}\}\s*(?:<\/noinclude>)?\s*/gi;
var re_result = oldtag_re.exec(text);
if (re_result) {
if (params.tag === 'none' || confirm('{{' + re_result[1] + '}} در صفحه یافت نشد. \nبرای حذف آن تأیید و برای نگهداشتن آن لغو را کلیک کنید.')) { // localized
text = text.replace(oldtag_re, '');
}
}
if (params.tag === 'none') {
summary = 'حذف الگوی حفاظت' + Twinkle.getPref('summaryAd'); // localized
} else {
tag = params.tag;
if (params.reason) {
tag += '|reason=' + params.reason;
}
if (params.small) {
tag += '|small=yes';
}
if (/^\s*#redirect/i.test(text)) { // redirect page
// Only tag if no {{rcat shell}} is found
if (!text.match(/{{(?:redr|this is a redirect|r(?:edirect)?(?:.?cat.*)?[ _]?sh)/i)) {
text = text.replace(/#REDIRECT ?(\[\[.*?\]\])(.*)/i, '#REDIRECT $1$2\n\n{{' + tag + '}}');
} else {
Morebits.status.info('پوستهٔ ردهٔ تغییر مسیر موجود است', 'کاری برای انجام وجود ندارد'); // localized
return;
}
} else if (params.noinclude) {
text = '<noinclude>{{' + tag + '}}</noinclude>' + text;
} else {
text = '{{' + tag + '}}\n' + text;
}
summary = 'افزودن {{' + params.tag + '}}' + Twinkle.getPref('summaryAd'); // localized
}
protectedPage.setEditSummary(summary);
protectedPage.setPageText(text);
protectedPage.setCreateOption('nocreate');
protectedPage.suppressProtectWarning(); // no need to let admins know they are editing through protection
protectedPage.save();
},
fileRequest: function(rppPage) {
var params = rppPage.getCallbackParameters();
var text = rppPage.getPageText();
var statusElement = rppPage.getStatusElement();
var rppRe = new RegExp('===\\s*(\\[\\[)?\\s*:?\\s*' + RegExp.escape(Morebits.pageNameNorm, true) + '\\s*(\\]\\])?\\s*===', 'm');
var tag = rppRe.exec(text);
var rppLink = document.createElement('a');
rppLink.setAttribute('href', mw.util.getUrl(rppPage.getPageName()));
rppLink.appendChild(document.createTextNode(rppPage.getPageName()));
if (tag) {
statusElement.error([ 'در حال حاضر درخواست حفاظتی برای این صفحه در ', rppLink, ' موجود است، ادامه نمیدهیم' ]); // localized
return;
}
var newtag = '=== [[:' + Morebits.pageNameNorm + ']] ===\n';
if (new RegExp('^' + RegExp.escape(newtag).replace(/\s+/g, '\\s*'), 'm').test(text)) {
statusElement.error([ 'در حال حاضر درخواست حفاظتی برای این صفحه در ', rppLink, ' موجود است، ادامه نمیدهیم' ]); // localized
return;
}
newtag += '* {{pagelinks|1=' + Morebits.pageNameNorm + '}}\n\n';
var words;
switch (params.expiry) {
case 'temporary':
words = 'موقت '; // localized
break;
case 'indefinite':
words = 'بیپایان '; // localized
break;
default:
words = '';
break;
}
words += params.typename;
newtag += "'''" + Morebits.string.toUpperCaseFirstChar(words) + (params.reason !== '' ? ":''' " +
Morebits.string.formatReasonText(params.reason) : ".'''") + ' ~~~~';
// If either protection type results in a increased status, then post it under increase
// else we post it under decrease
var increase = false;
var protInfo = Twinkle.protect.protectionPresetsInfo[params.category];
// function to compute protection weights (see comment at Twinkle.protect.protectionWeight)
var computeWeight = function(mainLevel, stabilizeLevel) {
var result = Twinkle.protect.protectionWeight[mainLevel || 'all'];
if (stabilizeLevel) {
if (result) {
if (stabilizeLevel.level === 'autoconfirmed') {
result += 2;
}
} else {
result = Twinkle.protect.protectionWeight['flaggedrevs_' + stabilizeLevel];
}
}
return result;
};
// compare the page's current protection weights with the protection we are requesting
var editWeight = computeWeight(Twinkle.protect.currentProtectionLevels.edit &&
Twinkle.protect.currentProtectionLevels.edit.level,
Twinkle.protect.currentProtectionLevels.stabilize &&
Twinkle.protect.currentProtectionLevels.stabilize.level);
if (computeWeight(protInfo.edit, protInfo.stabilize) > editWeight ||
computeWeight(protInfo.move) > computeWeight(Twinkle.protect.currentProtectionLevels.move &&
Twinkle.protect.currentProtectionLevels.move.level) ||
computeWeight(protInfo.create) > computeWeight(Twinkle.protect.currentProtectionLevels.create &&
Twinkle.protect.currentProtectionLevels.create.level)) {
increase = true;
}
var reg;
if (increase) {
reg = /(\n==\s*Current requests for reduction in protection level\s*==)/;
} else {
reg = /(\n==\s*Current requests for edits to a protected page\s*==)/;
}
var originalTextLength = text.length;
text = text.replace(reg, '\n' + newtag + '\n$1');
if (text.length === originalTextLength) {
var linknode = document.createElement('a');
linknode.setAttribute('href', mw.util.getUrl('Wikipedia:Twinkle/Fixing RPP'));
linknode.appendChild(document.createTextNode('How to fix RPP'));
statusElement.error([ 'عنوانی مناسب در وو:دمص یافت نشد. برای حل این مشکل ', linknode, ' را ببینید' ]); // localized
return;
}
statusElement.status('افزودن درخواست جدید...'); // localized
rppPage.setEditSummary('/* ' + Morebits.pageNameNorm + ' */ Requesting ' + params.typename + (params.typename === 'pending changes' ? ' on [[:' : ' of [[:') +
Morebits.pageNameNorm + ']].' + Twinkle.getPref('summaryAd'));
rppPage.setPageText(text);
rppPage.setCreateOption('recreate');
rppPage.save();
}
};
})(jQuery);
// </nowiki>
musj5pe1zmh23qn9nqacvk8mu68wtyg
کاربر:Mamouri/twinkle/friendlytag-2020.js
2
219495
909002
2026-07-30T16:05:11Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
909002
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** friendlytag.js: Tag module
****************************************
* Mode of invocation: Tab ("Tag")
* Active on: Existing articles and drafts; file pages with a corresponding file
* which is local (not on Commons); all redirects
*/
Twinkle.tag = function friendlytag() {
// redirect tagging
if (Morebits.wiki.isPageRedirect()) {
Twinkle.tag.mode = 'redirect';
Twinkle.addPortletLink(Twinkle.tag.callback, 'برچسب', 'friendly-tag', 'تغییرمسیر برچسب'); // localized
// file tagging
} else if (mw.config.get('wgNamespaceNumber') === 6 && !document.getElementById('mw-sharedupload') && document.getElementById('mw-imagepage-section-filehistory')) {
Twinkle.tag.mode = 'file';
Twinkle.addPortletLink(Twinkle.tag.callback, 'برچسب', 'friendly-tag', 'زدن برچسبهای نگهداری به پرونده'); // localized
// article/draft article tagging
} else if ([0, 118].indexOf(mw.config.get('wgNamespaceNumber')) !== -1 && mw.config.get('wgCurRevisionId')) {
Twinkle.tag.mode = 'article';
// Can't remove tags when not viewing current version
Twinkle.tag.canRemove = (mw.config.get('wgCurRevisionId') === mw.config.get('wgRevisionId')) &&
// Disabled on latest diff because the diff slider could be used to slide
// away from the latest diff without causing the script to reload
!mw.config.get('wgDiffNewId');
Twinkle.addPortletLink(Twinkle.tag.callback, 'برچسب', 'friendly-tag', 'افزودن یا حذف برچسبهای نگهداری مقاله'); // localized
}
};
Twinkle.tag.checkedTags = [];
Twinkle.tag.callback = function friendlytagCallback() {
var Window = new Morebits.simpleWindow(630, Twinkle.tag.mode === 'article' ? 500 : 400);
Window.setScriptName('Twinkle');
// anyone got a good policy/guideline/info page/instructional page link??
Window.addFooterLink('Twinkle help', 'WP:TW/DOC#tag');
var form = new Morebits.quickForm(Twinkle.tag.callback.evaluate);
form.append({
type: 'input',
label: 'پالایش سریع: ', // localized
name: 'quickfilter',
size: '30px',
event: function twinkletagquickfilter() {
// flush the DOM of all existing underline spans
$allCheckboxDivs.find('.search-hit').each(function(i, e) {
var label_element = e.parentElement;
// This would convert <label>Hello <span class=search-hit>wo</span>rld</label>
// to <label>Hello world</label>
label_element.innerHTML = label_element.textContent;
});
if (this.value) {
$allCheckboxDivs.hide();
$allHeaders.hide();
var searchString = this.value;
var searchRegex = new RegExp(mw.util.escapeRegExp(searchString), 'i');
$allCheckboxDivs.find('label').each(function () {
var label_text = this.textContent;
var searchHit = searchRegex.exec(label_text);
if (searchHit) {
var range = document.createRange();
var textnode = this.childNodes[0];
range.selectNodeContents(textnode);
range.setStart(textnode, searchHit.index);
range.setEnd(textnode, searchHit.index + searchString.length);
var underline_span = $('<span>').addClass('search-hit').css('text-decoration', 'underline')[0];
range.surroundContents(underline_span);
this.parentElement.style.display = 'block'; // show
}
});
} else {
$allCheckboxDivs.show();
$allHeaders.show();
}
}
});
switch (Twinkle.tag.mode) {
case 'article':
Window.setTitle('Article maintenance tagging');
form.append({
type: 'select',
name: 'sortorder',
label: 'مشاهده این فهرست:', // localized
tooltip: 'میتوانید ترتیب پیشفرض نمایش را در ترجیحات توینکل خود تغییر دهید (وو:توینکل تر).', // localized
event: Twinkle.tag.updateSortOrder,
list: [
{ type: 'option', value: 'cat', label: 'بر پایه رده', selected: Twinkle.getPref('tagArticleSortOrder') === 'cat' }, // localized
{ type: 'option', value: 'alpha', label: 'بهترتیب الفبا', selected: Twinkle.getPref('tagArticleSortOrder') === 'alpha' } // localized
]
});
if (!Twinkle.tag.canRemove) {
var divElement = document.createElement('div');
divElement.innerHTML = 'برای حذف برچسبهای موجود، لطفاً منوی برچسب را از نسخهٔ کنونی مقاله باز کنید'; // localized
form.append({
type: 'div',
name: 'untagnotice',
label: divElement
});
}
form.append({
type: 'div',
id: 'tagWorkArea',
className: 'morebits-scrollbox',
style: 'max-height: 28em'
});
form.append({
type: 'checkbox',
list: [
{
label: 'گروهبندی برچسبها در {{مشکلات متعدد}}، در صورت امکان', // localized
value: 'group',
name: 'group',
tooltip: 'در صورتی که در حال افزودن دو یا چند الگویی که توسط {{مشکلات متعدد}} پشتیبانی میشوند هستید و این گزینه نیز انتخاب شدهاست، تمامی الگوهای پشتیبانیشده در درون الگوی {{مشکلات متعدد}} گروهبندی خواهند شد.', // localized
checked: Twinkle.getPref('groupByDefault')
}
]
});
form.append({
type: 'input',
label: 'دلیل',
name: 'reason',
tooltip: 'دلیل اختیاری برای پیوستشدن به خلاصه ویرایش. ذکر دلیل بههنگام حذف برچسبها توصیه میشود.', // localized
size: '60px'
});
break;
case 'file':
Window.setTitle('File maintenance tagging');
form.append({ type: 'header', label: 'برچسبهای مشکلات مجوز و منبعدهی' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.licenseList });
form.append({ type: 'header', label: 'برچسبهای مرتبط با ویکیانبار' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.commonsList });
form.append({ type: 'header', label: 'برچسبهای تمیزکاری' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.cleanupList });
form.append({ type: 'header', label: 'برچسبهای کیفیت تصویر' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.qualityList });
form.append({ type: 'header', label: 'برچسبهای جایگزینی' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.tag.file.replacementList });
if (Twinkle.getPref('customFileTagList').length) {
form.append({ type: 'header', label: 'برچسبهای سفارشی' }); // localized
form.append({ type: 'checkbox', name: 'fileTags', list: Twinkle.getPref('customFileTagList') });
}
break;
case 'redirect':
Window.setTitle('Redirect tagging');
form.append({ type: 'header', label: 'Spelling, misspelling, tense and capitalization templates' });
form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.spellingList });
form.append({ type: 'header', label: 'الگوهای نام جایگزین' }); // localized
form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.alternativeList });
form.append({ type: 'header', label: 'الگوهای تغییرمسیر گوناگون و مدیریتی' }); // localized
form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.administrativeList });
if (Twinkle.getPref('customRedirectTagList').length) {
form.append({ type: 'header', label: 'برچسبهای سفارشی' }); // localized
form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.getPref('customRedirectTagList') });
}
break;
default:
alert('Twinkle.tag: unknown mode ' + Twinkle.tag.mode);
break;
}
if (document.getElementsByClassName('patrollink').length) {
form.append({
type: 'checkbox',
list: [
{
label: 'علامت زدن صفحه بهعنوان گشتخورده/بازبینیشده', // localized
value: 'patrolPage',
name: 'patrolPage',
checked: Twinkle.getPref('markTaggedPagesAsPatrolled')
}
]
});
}
form.append({ type: 'submit', className: 'tw-tag-submit' });
var result = form.render();
Window.setContent(result);
Window.display();
// for quick filter:
$allCheckboxDivs = $(result).find('[name$=Tags]').parent();
$allHeaders = $(result).find('h5');
result.quickfilter.focus(); // place cursor in the quick filter field as soon as window is opened
result.quickfilter.autocomplete = 'off'; // disable browser suggestions
result.quickfilter.addEventListener('keypress', function(e) {
if (e.keyCode === 13) { // prevent enter key from accidentally submitting the form
e.preventDefault();
return false;
}
});
if (Twinkle.tag.mode === 'article') {
Twinkle.tag.alreadyPresentTags = [];
if (Twinkle.tag.canRemove) {
// Look for existing maintenance tags in the lead section and put them in array
// All tags are HTML table elements that are direct children of .mw-parser-output,
// except when they are within {{multiple issues}}
$('.mw-parser-output').children().each(function parsehtml(i, e) {
// break out on encountering the first heading, which means we are no
// longer in the lead section
if (e.tagName === 'H2') {
return false;
}
// The ability to remove tags depends on the template's {{ambox}} |name=
// parameter bearing the template's correct name (preferably) or a name that at
// least redirects to the actual name
// All tags have their first class name as "box-" + template name
if (e.className.indexOf('box-') === 0) {
if (e.classList[0] === 'box-Multiple_issues') {
$(e).find('.ambox').each(function(idx, e) {
var tag = e.classList[0].slice(4).replace(/_/g, ' ');
Twinkle.tag.alreadyPresentTags.push(tag);
});
return true; // continue
}
var tag = e.classList[0].slice(4).replace(/_/g, ' ');
Twinkle.tag.alreadyPresentTags.push(tag);
}
});
// {{Uncategorized}} and {{Improve categories}} are usually placed at the end
if ($('.box-Uncategorized').length) {
Twinkle.tag.alreadyPresentTags.push('Uncategorized');
}
if ($('.box-Improve_categories').length) {
Twinkle.tag.alreadyPresentTags.push('Improve categories');
}
}
// Add status text node after Submit button
var statusNode = document.createElement('small');
statusNode.id = 'tw-tag-status';
Twinkle.tag.status = {
// initial state; defined like this because these need to be available for reference
// in the click event handler
numAdded: 0,
numRemoved: 0
};
$('button.tw-tag-submit').after(statusNode);
// fake a change event on the sort dropdown, to initialize the tag list
var evt = document.createEvent('Event');
evt.initEvent('change', true, true);
result.sortorder.dispatchEvent(evt);
} else {
// Redirects and files: Add a link to each template's description page
Morebits.quickForm.getElements(result, Twinkle.tag.mode + 'Tags').forEach(generateLinks);
}
};
// $allCheckboxDivs and $allHeaders are defined globally, rather than in the
// quickfilter event function, to avoid having to recompute them on every keydown
var $allCheckboxDivs, $allHeaders;
Twinkle.tag.updateSortOrder = function(e) {
var form = e.target.form;
var sortorder = e.target.value;
Twinkle.tag.checkedTags = form.getChecked('articleTags') || [];
var container = new Morebits.quickForm.element({ type: 'fragment' });
// function to generate a checkbox, with appropriate subgroup if needed
var makeCheckbox = function(tag, description) {
var checkbox = { value: tag, label: '{{' + tag + '}}: ' + description };
if (Twinkle.tag.checkedTags.indexOf(tag) !== -1) {
checkbox.checked = true;
}
switch (tag) {
case 'Cleanup':
checkbox.subgroup = {
name: 'cleanup',
type: 'input',
label: 'دلیل مشخص برای اینکه چرا به تمیزکاری نیاز است: ', // localized
tooltip: 'الزامی.', // localized
size: 35
};
break;
case 'Close paraphrasing':
checkbox.subgroup = {
name: 'closeParaphrasing',
type: 'input',
label: 'منبع: ', // localized
tooltip: 'منبعی که بهطور نزدیک بازنویسی شدهاست' // localized
};
break;
case 'Copy edit':
checkbox.subgroup = {
name: 'copyEdit',
type: 'input',
label: '"این مقاله ممکن است نیازمند ویرایستاری باشد..." ', // localized
tooltip: 'بهعنوان مثال، "املای یکپارچه". انتخابی.', // localized
size: 35
};
break;
case 'Copypaste':
checkbox.subgroup = {
name: 'copypaste',
type: 'input',
label: 'نشانی منبع: ', // localized
tooltip: 'اگر مشخص است.', // localized
size: 50
};
break;
case 'Expand language':
checkbox.subgroup = [ {
name: 'expandLanguageLangCode',
type: 'input',
label: 'شناسه زبان: ', // localized
tooltip: 'شناسهٔ زبانی که مقاله باید از روی آن گسترش دادهشود' // localized
}, {
name: 'expandLanguageArticle',
type: 'input',
label: 'عنوان مقاله: ', // localized
tooltip: 'عنوان مقالهای که گسترش از روی آن انجام میشود، بدون پیشوند میانویکی' // localized
}
];
break;
case 'Expert needed':
checkbox.subgroup = [
{
name: 'expertNeeded',
type: 'input',
label: 'عنوان ویکیپروژهٔ مرتبط: ', // localized
tooltip: 'بهطور انتخابی، نام ویکیپروژهای که میتواند به جذب یک کاربر باتجربه کمک کند را وارد کنید. بدون پیشوند «ویکیپروژه».' // localized
},
{
name: 'expertNeededReason',
type: 'input',
label: 'دلیل: ', // localized
tooltip: 'توضیح کوتاه برای توصیف مشکل. پیوند به دلیل یا بحث الزامی است.' // localized
},
{
name: 'expertNeededTalk',
type: 'input',
label: 'ریسهٔ بحث: ', // localized
tooltip: 'عنوان بخشی از صفحهٔ بحث این مقاله که بحث دربارهٔ مشکل در آن جریان دارد. نیاز به درج پیوند نیست، تنها عنوان بخش را وارد کنید. پیوند به دلیل یا بحث الزامی است.' // localized
}
];
break;
case 'Globalize':
checkbox.subgroup = {
name: 'globalizeRegion',
type: 'input',
label: 'منطقه یا کشورِ بیش از اندازه معرفیشده' // localized
};
break;
case 'History merge':
checkbox.subgroup = [
{
name: 'histmergeOriginalPage',
type: 'input',
label: 'مقالهٔ دیگر: ', // localized
tooltip: 'عنوان صفحهای که میتواند در این مقاله ادغام شود (الزامی).' // localized
},
{
name: 'histmergeReason',
type: 'input',
label: 'دلیل: ', // localized
tooltip: 'توضیح کوتاه که دلیل نیاز به ادغام تاریخچه را بیان کند. بهتر است با عبارت «زیرا» آغاز شده و با یک نقطه پایان یابد.' // localized
},
{
name: 'histmergeSysopDetails',
type: 'input',
label: 'جزئیات بیشتر: ', // localized
tooltip: 'برای موارد پیچیده، راهنماییهای بیشتری را در اختیار مدیر بررسیکننده قرار دهید.' // localized
}
];
break;
case 'Merge':
case 'Merge from':
case 'Merge to':
var otherTagName = 'Merge';
switch (tag) {
case 'Merge from':
otherTagName = 'Merge to';
break;
case 'Merge to':
otherTagName = 'Merge from';
break;
// no default
}
checkbox.subgroup = [
{
name: 'mergeTarget',
type: 'input',
label: 'مقاله(های) دیگر: ', // localized
tooltip: 'اگر قصد دارید مقالههای متعدد را مشخص کنید، آنها را با نویسهٔ لوله از هم جدا کنید: مقالهٔ اول|مقالهٔ دوم' // localized
},
{
name: 'mergeTagOther',
type: 'checkbox',
list: [
{
label: 'مقالهٔ دیگر را با یک برچسب {{' + otherTagName + '}} مشخص کنید', // localized
checked: true,
tooltip: 'فقط در صورتی که تنها عنوان یک مقاله وارد شدهباشد، در دسترس است.' // localized
}
]
}
];
if (mw.config.get('wgNamespaceNumber') === 0) {
checkbox.subgroup.push({
name: 'mergeReason',
type: 'textarea',
label: 'دلیل اصلی برای ادغام (بر روی صفحهٔ بحث' +
(tag === 'Merge to' ? 'the other article\'s' : 'this article\'s') + ' قرار خواهد گرفت):', // localized
tooltip: 'انتخابی، اما بهشدت توصیهشده. در صورت عدم تمایل، خالی بگذارید. فقط در صورتی که تنها عنوان یک مقاله وارد شدهباشد، در دسترس است.' // localized
});
}
break;
case 'Not English':
case 'Rough translation':
checkbox.subgroup = [
{
name: 'translationLanguage',
type: 'input',
label: 'زبان مقاله (اگر مشخص است): ', // localized
tooltip: 'برای راهنمایی، مرور [[en:WP:LRC]] را در نظر داشتهباشید. اگر مقاله را در صفحهٔ مقالههای نیازمند ترجمه فهرست میکنید، لطفاً از خالیگذاشتن این جعبه خودداری کنید؛ مگر آنکه کاملاً نامطمؤن باشید.' // localized
}
];
if (tag === 'Not English') {
checkbox.subgroup.push({
name: 'translationNotify',
type: 'checkbox',
list: [
{
label: 'اطلاعرسانی به سازندهٔ مقاله', // localized
checked: true,
tooltip: "الگوی {{uw-notenglish}} را در صفحهٔ بحث سازندهٔ مقاله قرار میدهد." // localized
}
]
});
}
if (mw.config.get('wgNamespaceNumber') === 0) {
checkbox.subgroup.push({
name: 'translationPostAtPNT',
type: 'checkbox',
list: [
{
label: 'فهرستکردن این مقاله در ویکیپدیا:صفحههای نیازمند ترجمه به فارسی (PNT)', // localized
checked: true
}
]
});
checkbox.subgroup.push({
name: 'translationComments',
type: 'textarea',
label: 'توضیح بیشتر برای ارسال به ویکیپدیا:صفحههای نیازمند ترجمه به فارسی', // localized
tooltip: 'انتخابی، و متناسب تنها در صورتی که "فهرستکردن این مقاله..." در بالا انتخاب شدهباشد.' // localized
});
}
break;
case 'Notability':
checkbox.subgroup = {
name: 'notability',
type: 'select',
list: [
{ label: "{{سرشناسی}}: موضوع مقاله ممکن است رهنمود اصلی سرشناسی را احراز نکند", value: 'none' }, // localized
{ label: '{{سرشناسی|دانشگاهیان}}: رهنمود سرشناسی دانشگاهیان', value: 'Academics' }, // localized
{ label: '{{سرشناسی|اجرام آسمانی}}: رهنمود سرشناسی اجرام آسمانی', value: 'Astro' }, // localized
{ label: '{{سرشناسی|افراد}}: رهنمود سرشناسی زندگینامه افراد', value: 'Biographies' }, // localized
{ label: '{{سرشناسی|کتابها}}: رهنمود سرشناسی کتاب', value: 'Books' }, // localized
{ label: '{{سرشناسی|سازمانها}}: رهنمودهای سرشناسی شرکتها و سازمانها', value: 'Companies' }, // localized
{ label: '{{سرشناسی|رویدادها}}: رهنمود سرشناسی برای رویدادها', value: 'Events' }, // localized
{ label: '{{سرشناسی|فیلمها}}: رهنمود سرشناسی فیلمها', value: 'Films' }, // localized
{ label: '{{سرشناسی|مکان}}: رهنمود سرشناسی ویژگیهای جغرافیایی', value: 'Geographic' }, // localized
{ label: '{{سرشناسی|فهرستها}}: رهنمود سرشناسی فهرستهای مسقل', value: 'Lists' }, // localized
{ label: '{{سرشناسی|موسیقی}}: رهنمود سرشناسی موسیقی', value: 'Music' }, // localized
{ label: '{{سرشناسی|نوواژهها}}: رهنمود سرشناسی نوواژهها', value: 'Neologisms' }, // localized
{ label: '{{سرشناسی|عددها}}: رهنمود سرشناسی عددها', value: 'Numbers' }, // localized
{ label: '{{سرشناسی|محصولات}}: رهنمود سرشناسی محصولات و خدمات', value: 'Products' }, // localized
{ label: '{{سرشناسی|ورزشها}}: رهنمود سرشناسی ورزشها', value: 'Sports' }, // localized
{ label: '{{سرشناسی|تلویزیون}}: رهنمود سرشناسی برنامههای تلویزیونی', value: 'Television' }, // localized
{ label: '{{سرشناسی|وب}}: رهنمود سرشناسی مجتوای وب', value: 'Web' } // localized
]
};
break;
default:
break;
}
return checkbox;
};
var makeCheckboxesForAlreadyPresentTags = function() {
container.append({ type: 'header', id: 'tagHeader0', label: 'Tags already present' });
var subdiv = container.append({ type: 'div', id: 'tagSubdiv0' });
var checkboxes = [];
var unCheckedTags = e.target.form.getUnchecked('alreadyPresentArticleTags') || [];
Twinkle.tag.alreadyPresentTags.forEach(function(tag) {
var description = Twinkle.tag.article.tags[tag];
var checkbox =
{
value: tag,
label: '{{' + tag + '}}' + (description ? ': ' + description : ''),
checked: unCheckedTags.indexOf(tag) === -1,
style: 'font-style: italic'
};
checkboxes.push(checkbox);
});
subdiv.append({
type: 'checkbox',
name: 'alreadyPresentArticleTags',
list: checkboxes
});
};
if (sortorder === 'cat') { // categorical sort order
// function to iterate through the tags and create a checkbox for each one
var doCategoryCheckboxes = function(subdiv, array) {
var checkboxes = [];
$.each(array, function(k, tag) {
var description = Twinkle.tag.article.tags[tag];
if (Twinkle.tag.alreadyPresentTags.indexOf(tag) === -1) {
checkboxes.push(makeCheckbox(tag, description));
}
});
subdiv.append({
type: 'checkbox',
name: 'articleTags',
list: checkboxes
});
};
if (Twinkle.tag.alreadyPresentTags.length > 0) {
makeCheckboxesForAlreadyPresentTags();
}
var i = 1;
// go through each category and sub-category and append lists of checkboxes
$.each(Twinkle.tag.article.tagCategories, function(title, content) {
container.append({ type: 'header', id: 'tagHeader' + i, label: title }); // localized
var subdiv = container.append({ type: 'div', id: 'tagSubdiv' + i++ });
if (Array.isArray(content)) {
doCategoryCheckboxes(subdiv, content);
} else {
$.each(content, function(subtitle, subcontent) {
subdiv.append({ type: 'div', label: [ Morebits.htmlNode('b', subtitle) ] });
doCategoryCheckboxes(subdiv, subcontent);
});
}
});
} else { // alphabetical sort order
if (Twinkle.tag.alreadyPresentTags.length > 0) {
makeCheckboxesForAlreadyPresentTags();
container.append({ type: 'header', id: 'tagHeader1', label: 'برچسبهای قابل استفاده' }); // localized
}
var checkboxes = [];
$.each(Twinkle.tag.article.tags, function(tag, description) {
if (Twinkle.tag.alreadyPresentTags.indexOf(tag) === -1) {
checkboxes.push(makeCheckbox(tag, description));
}
});
container.append({
type: 'checkbox',
name: 'articleTags',
list: checkboxes
});
}
// append any custom tags
if (Twinkle.getPref('customTagList').length) {
container.append({ type: 'header', label: 'برچسبهای سفارشی' }); // localized
container.append({ type: 'checkbox', name: 'articleTags',
list: Twinkle.getPref('customTagList').map(function(el) {
el.checked = Twinkle.tag.checkedTags.indexOf(el.value) !== -1;
return el;
})
});
}
var $workarea = $(form).find('#tagWorkArea');
var rendered = container.render();
$workarea.empty().append(rendered);
// for quick filter:
$allCheckboxDivs = $workarea.find('[name$=Tags]').parent();
$allHeaders = $workarea.find('h5, .quickformDescription');
form.quickfilter.value = ''; // clear search, because the search results are not preserved over mode change
form.quickfilter.focus();
// style adjustments
$workarea.find('h5').css({ 'font-size': '110%' });
$workarea.find('h5:not(:first-child)').css({ 'margin-top': '1em' });
$workarea.find('div').filter(':has(span.quickformDescription)').css({ 'margin-top': '0.4em' });
var alreadyPresentTags = Morebits.quickForm.getElements(form, 'alreadyPresentArticleTags');
if (alreadyPresentTags) {
alreadyPresentTags.forEach(generateLinks);
}
// in the unlikely case that *every* tag is already on the page
var notPresentTags = Morebits.quickForm.getElements(form, 'articleTags');
if (notPresentTags) {
notPresentTags.forEach(generateLinks);
}
// tally tags added/removed, update statusNode text
var statusNode = document.getElementById('tw-tag-status');
$('[name=articleTags], [name=alreadyPresentArticleTags]').click(function() {
if (this.name === 'articleTags') {
Twinkle.tag.status.numAdded += this.checked ? 1 : -1;
} else if (this.name === 'alreadyPresentArticleTags') {
Twinkle.tag.status.numRemoved += this.checked ? -1 : 1;
}
var firstPart = 'Adding ' + Twinkle.tag.status.numAdded + ' tag' + (Twinkle.tag.status.numAdded > 1 ? 's' : '');
var secondPart = 'Removing ' + Twinkle.tag.status.numRemoved + ' tag' + (Twinkle.tag.status.numRemoved > 1 ? 's' : '');
statusNode.textContent =
(Twinkle.tag.status.numAdded ? ' ' + firstPart : '') +
(Twinkle.tag.status.numRemoved ? (Twinkle.tag.status.numAdded ? '; ' : ' ') + secondPart : '');
});
};
/**
* Adds a link to each template's description page
* @param {Morebits.quickForm.element} checkbox associated with the template
*/
var generateLinks = function(checkbox) {
var link = Morebits.htmlNode('a', '>');
link.setAttribute('class', 'tag-template-link');
var tagname = checkbox.values;
link.setAttribute('href', mw.util.getUrl(
(tagname.indexOf(':') === -1 ? 'Template:' : '') +
(tagname.indexOf('|') === -1 ? tagname : tagname.slice(0, tagname.indexOf('|')))
));
link.setAttribute('target', '_blank');
$(checkbox).parent().append(['\u00A0', link]);
};
// Tags for ARTICLES start here
Twinkle.tag.article = {};
// A list of all article tags, in alphabetical order
// To ensure tags appear in the default "categorized" view, add them to the tagCategories hash below.
Twinkle.tag.article.tags = {
'ویکیسازی': 'نیازمند ویکیسازی',
'منبع': 'نیازمند منبع',
'ابهامزدایی': 'ابهامزدایی'
};
// A list of tags in order of category
// Tags should be in alphabetical order within the categories
// Add new categories with discretion - the list is long enough as is!
Twinkle.tag.article.tagCategories = {
'برچسبهای نگهداری': [
'ویکیسازی',
'منبع',
'ابهامزدایی'
]
};
// Contains those article tags that *do not* work inside {{multiple issues}}.
Twinkle.tag.multipleIssuesExceptions = [
'Copypaste',
'Current', // Works but not intended for use in MI
'Expand language',
'GOCEinuse',
'History merge',
'Improve categories',
'In use',
'Merge',
'Merge from',
'Merge to',
'Not English',
'Rough translation',
'Uncategorized',
'Under construction'
];
// Tags for REDIRECTS start here
Twinkle.tag.spellingList = [];
Twinkle.tag.alternativeList = [];
Twinkle.tag.administrativeList = [];
// maintenance tags for FILES start here
Twinkle.tag.file = {};
Twinkle.tag.file.licenseList = [];
Twinkle.tag.file.commonsList = [];
Twinkle.tag.file.cleanupList = [];
Twinkle.tag.file.qualityList = [];
Twinkle.tag.file.replacementList = [];
Twinkle.tag.callbacks = {
article: function articleCallback(pageobj) {
// Remove tags that become superfluous with this action
var pageText = pageobj.getPageText().replace(/\{\{\s*([Uu]serspace draft)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/g, '');
var params = pageobj.getCallbackParameters();
/**
* Saves the page following the removal of tags if any. The last step.
* Called from removeTags()
*/
var postRemoval = function() {
if (params.tagsToRemove.length) {
// Remove empty {{multiple issues}} if found
pageText = pageText.replace(/\{\{(multiple ?issues|article ?issues|mi)\s*\|\s*\}\}\n?/im, '');
// Remove single-element {{multiple issues}} if found
pageText = pageText.replace(/\{\{(?:multiple ?issues|article ?issues|mi)\s*\|\s*(\{\{[^}]+\}\})\s*\}\}/im, '$1');
}
// Build edit summary
var makeSentence = function(array) {
if (array.length < 3) {
return array.join(' and ');
}
var last = array.pop();
return array.join(', ') + ', and ' + last;
};
var makeTemplateLink = function(tag) {
var text = '{{[[';
// if it is a custom tag with a parameter
if (tag.indexOf('|') !== -1) {
tag = tag.slice(0, tag.indexOf('|'));
}
text += tag.indexOf(':') !== -1 ? tag : 'Template:' + tag + '|' + tag;
return text + ']]}}';
};
var summaryText;
var addedTags = params.tags.map(makeTemplateLink);
var removedTags = params.tagsToRemove.map(makeTemplateLink);
if (addedTags.length) {
summaryText = 'Added ' + makeSentence(addedTags);
summaryText += removedTags.length ? '; and removed ' + makeSentence(removedTags) : '';
} else {
summaryText = 'Removed ' + makeSentence(removedTags);
}
summaryText += ' tag' + (addedTags.length + removedTags.length > 1 ? 's' : '');
if (params.reason) {
summaryText += ': ' + params.reason;
}
// avoid truncated summaries
if (summaryText.length > (499 - Twinkle.getPref('summaryAd').length)) {
summaryText = summaryText.replace(/\[\[[^|]+\|([^\]]+)\]\]/g, '$1');
}
pageobj.setPageText(pageText);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages'));
pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor'));
pageobj.setCreateOption('nocreate');
pageobj.save(function() {
// special functions for merge tags
if (params.mergeReason) {
// post the rationale on the talk page (only operates in main namespace)
var talkpageText = '\n\n== ' + params.talkDiscussionTitleLinked + ' ==\n\n';
talkpageText += params.mergeReason.trim() + ' ~~~~';
var talkpage = new Morebits.wiki.page('Talk:' + params.discussArticle, 'Posting rationale on talk page');
talkpage.setAppendText(talkpageText);
talkpage.setEditSummary('/* ' + params.talkDiscussionTitle + ' */ new section' + Twinkle.getPref('summaryAd'));
talkpage.setWatchlist(Twinkle.getPref('watchMergeDiscussions'));
talkpage.setCreateOption('recreate');
talkpage.append();
}
if (params.mergeTagOther) {
// tag the target page if requested
var otherTagName = 'Merge';
if (params.mergeTag === 'Merge from') {
otherTagName = 'Merge to';
} else if (params.mergeTag === 'Merge to') {
otherTagName = 'Merge from';
}
var newParams = {
tags: [otherTagName],
tagsToRemove: [],
tagsToRemain: [],
mergeTarget: Morebits.pageNameNorm,
discussArticle: params.discussArticle,
talkDiscussionTitle: params.talkDiscussionTitle,
talkDiscussionTitleLinked: params.talkDiscussionTitleLinked
};
var otherpage = new Morebits.wiki.page(params.mergeTarget, 'Tagging other page (' +
params.mergeTarget + ')');
otherpage.setCallbackParameters(newParams);
otherpage.load(Twinkle.tag.callbacks.article);
}
// post at WP:PNT for {{not English}} and {{rough translation}} tag
if (params.translationPostAtPNT) {
var pntPage = new Morebits.wiki.page('Wikipedia:Pages needing translation into English',
'Listing article at Wikipedia:Pages needing translation into English');
pntPage.setFollowRedirect(true);
pntPage.load(function friendlytagCallbacksTranslationListPage(pageobj) {
var old_text = pageobj.getPageText();
var template = params.tags.indexOf('Rough translation') !== -1 ? 'duflu' : 'needtrans';
var lang = params.translationLanguage;
var reason = params.translationComments;
var templateText = '{{subst:' + template + '|pg=' + Morebits.pageNameNorm + '|Language=' +
(lang || 'uncertain') + '|Comments=' + reason.trim() + '}} ~~~~';
var text, summary;
if (template === 'duflu') {
text = old_text + '\n\n' + templateText;
summary = 'Translation cleanup requested on ';
} else {
text = old_text.replace(/\n+(==\s?Translated pages that could still use some cleanup\s?==)/,
'\n\n' + templateText + '\n\n$1');
summary = 'Translation' + (lang ? ' from ' + lang : '') + ' requested on ';
}
if (text === old_text) {
pageobj.getStatusElement().error('failed to find target spot for the discussion');
return;
}
pageobj.setPageText(text);
pageobj.setEditSummary(summary + ' [[:' + Morebits.pageNameNorm + ']]' + Twinkle.getPref('summaryAd'));
pageobj.setCreateOption('recreate');
pageobj.save();
});
}
if (params.translationNotify) {
pageobj.lookupCreation(function(innerPageobj) {
var initialContrib = innerPageobj.getCreator();
// Disallow warning yourself
if (initialContrib === mw.config.get('wgUserName')) {
innerPageobj.getStatusElement().warn('You (' + initialContrib + ') created this page; skipping user notification');
return;
}
var userTalkPage = new Morebits.wiki.page('User talk:' + initialContrib,
'Notifying initial contributor (' + initialContrib + ')');
var notifytext = '\n\n== Your article [[' + Morebits.pageNameNorm + ']]==\n' +
'{{subst:uw-notenglish|1=' + Morebits.pageNameNorm +
(params.translationPostAtPNT ? '' : '|nopnt=yes') + '}} ~~~~';
userTalkPage.setAppendText(notifytext);
userTalkPage.setEditSummary('Notice: Please use English when contributing to the English Wikipedia.' +
Twinkle.getPref('summaryAd'));
userTalkPage.setCreateOption('recreate');
userTalkPage.setFollowRedirect(true);
userTalkPage.append();
});
}
});
if (params.patrol) {
pageobj.triage();
}
};
/**
* Removes the existing tags that were deselected (if any)
* Calls postRemoval() when done
*/
var removeTags = function removeTags() {
if (params.tagsToRemove.length === 0) {
postRemoval();
return;
}
Morebits.status.info('Info', 'Removing deselected tags that were already present');
var getRedirectsFor = [];
// Remove the tags from the page text, if found in its proper name,
// otherwise moves it to `getRedirectsFor` array earmarking it for
// later removal
params.tagsToRemove.forEach(function removeTag(tag) {
var tag_re = new RegExp('\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]+)?\\}\\}\\n?');
if (tag_re.test(pageText)) {
pageText = pageText.replace(tag_re, '');
} else {
getRedirectsFor.push('Template:' + tag);
}
});
if (!getRedirectsFor.length) {
postRemoval();
return;
}
// Remove tags which appear in page text as redirects
var api = new Morebits.wiki.api('Getting template redirects', {
'action': 'query',
'prop': 'linkshere',
'titles': getRedirectsFor.join('|'),
'redirects': 1, // follow redirect if the class name turns out to be a redirect page
'lhnamespace': '10', // template namespace only
'lhshow': 'redirect',
'lhlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
}, function removeRedirectTag(apiobj) {
$(apiobj.responseXML).find('page').each(function(idx, page) {
var removed = false;
$(page).find('lh').each(function(idx, el) {
var tag = $(el).attr('title').slice(9);
var tag_re = new RegExp('\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]*)?\\}\\}\\n?');
if (tag_re.test(pageText)) {
pageText = pageText.replace(tag_re, '');
removed = true;
return false; // break out of $.each
}
});
if (!removed) {
Morebits.status.warn('Info', 'Failed to find {{' +
$(page).attr('title').slice(9) + '}} on the page... excluding');
}
});
postRemoval();
});
api.post();
};
if (!params.tags.length) {
removeTags();
return;
}
var tagRe, tagText = '', tags = [], groupableTags = [], groupableExistingTags = [];
// Executes first: addition of selected tags
/**
* Updates `tagText` with the syntax of `tagName` template with its parameters
* @param {number} tagIndex
* @param {string} tagName
*/
var addTag = function articleAddTag(tagIndex, tagName) {
var currentTag = '';
if (tagName === 'Uncategorized' || tagName === 'Improve categories') {
pageText += '\n\n{{' + tagName + '|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}';
} else {
currentTag += '{{' + tagName;
// fill in other parameters, based on the tag
switch (tagName) {
case 'Cleanup':
currentTag += '|reason=' + params.cleanup;
break;
case 'Close paraphrasing':
currentTag += '|source=' + params.closeParaphrasing;
break;
case 'Copy edit':
if (params.copyEdit) {
currentTag += '|for=' + params.copyEdit;
}
break;
case 'Copypaste':
if (params.copypaste) {
currentTag += '|url=' + params.copypaste;
}
break;
case 'Expand language':
currentTag += '|topic=';
currentTag += '|langcode=' + params.expandLanguageLangCode;
if (params.expandLanguageArticle !== null) {
currentTag += '|otherarticle=' + params.expandLanguageArticle;
}
break;
case 'Expert needed':
if (params.expertNeeded) {
currentTag += '|1=' + params.expertNeeded;
}
if (params.expertNeededTalk) {
currentTag += '|talk=' + params.expertNeededTalk;
}
if (params.expertNeededReason) {
currentTag += '|reason=' + params.expertNeededReason;
}
break;
case 'Globalize':
currentTag += '|1=article';
if (params.globalizeRegion) {
currentTag += '|2=' + params.globalizeRegion;
}
break;
case 'News release':
currentTag += '|1=article';
break;
case 'Notability':
if (params.notability !== 'none') {
currentTag += '|' + params.notability;
}
break;
case 'Not English':
case 'Rough translation':
if (params.translationLanguage) {
currentTag += '|1=' + params.translationLanguage;
}
if (params.translationPostAtPNT) {
currentTag += '|listed=yes';
}
break;
case 'History merge':
currentTag += '|originalpage=' + params.histmergeOriginalPage;
if (params.histmergeReason) {
currentTag += '|reason=' + params.histmergeReason;
}
if (params.histmergeSysopDetails) {
currentTag += '|details=' + params.histmergeSysopDetails;
}
break;
case 'Merge':
case 'Merge to':
case 'Merge from':
params.mergeTag = tagName;
// normalize the merge target for now and later
params.mergeTarget = Morebits.string.toUpperCaseFirstChar(params.mergeTarget.replace(/_/g, ' '));
currentTag += '|' + params.mergeTarget;
// link to the correct section on the talk page, for article space only
if (mw.config.get('wgNamespaceNumber') === 0 && (params.mergeReason || params.discussArticle)) {
if (!params.discussArticle) {
// discussArticle is the article whose talk page will contain the discussion
params.discussArticle = tagName === 'Merge to' ? params.mergeTarget : mw.config.get('wgTitle');
// nonDiscussArticle is the article which won't have the discussion
params.nonDiscussArticle = tagName === 'Merge to' ? mw.config.get('wgTitle') : params.mergeTarget;
var direction = '[[' + params.nonDiscussArticle + ']]' + (params.mergeTag === 'Merge' ? ' with ' : ' into ') + '[[' + params.discussArticle + ']]';
params.talkDiscussionTitleLinked = 'Proposed merge of ' + direction;
params.talkDiscussionTitle = params.talkDiscussionTitleLinked.replace(/\[\[(.*?)\]\]/g, '$1');
}
currentTag += '|discuss=Talk:' + params.discussArticle + '#' + params.talkDiscussionTitle;
}
break;
default:
break;
}
currentTag += '|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}\n';
tagText += currentTag;
}
};
/**
* Adds the tags which go outside {{multiple issues}}, either because
* these tags aren't supported in {{multiple issues}} or because
* {{multiple issues}} is not being added to the page at all
*/
var addUngroupedTags = function() {
$.each(tags, addTag);
// Smartly insert the new tags after any hatnotes or
// afd, csd, or prod templates or hatnotes. Regex is
// extra complicated to allow for templates with
// parameters and to handle whitespace properly.
pageText = pageText.replace(
new RegExp(
// leading whitespace
'^\\s*' +
// capture template(s)
'(?:((?:\\s*' +
// AfD is special, as the tag includes html comments before and after the actual template
'(?:<!--.*AfD.*\\n\\{\\{(?:Article for deletion\\/dated|AfDM).*\\}\\}\\n<!--.*(?:\\n<!--.*)?AfD.*(?:\\s*\\n))?|' + // trailing whitespace/newline needed since this subst's a newline
// begin template format
'\\{\\{\\s*(?:' +
// CSD
'db|delete|db-.*?|speedy deletion-.*?|' +
// PROD
'(?:proposed deletion|prod blp)\\/dated(?:\\s*\\|(?:concern|user|timestamp|help).*)+|' +
// various hatnote templates
'about|correct title|dablink|distinguish|for|other\\s?(?:hurricaneuses|people|persons|places|uses(?:of)?)|redirect(?:-acronym)?|see\\s?(?:also|wiktionary)|selfref|short description|the' +
// not a hatnote, but sometimes under a CSD or AfD
'|salt|proposed deletion endorsed' +
// end main template name, optionally with a number (such as redirect2)
')\\d*\\s*' +
// template parameters
'(\\|(?:\\{\\{[^{}]*\\}\\}|[^{}])*)?' +
// end template format
'\\}\\})+' +
// end capture
'(?:\\s*\\n)?)' +
// trailing whitespace
'\\s*)?',
'i'), '$1' + tagText
);
removeTags();
};
// Separate tags into groupable ones (`groupableTags`) and non-groupable ones (`tags`)
params.tags.forEach(function(tag) {
tagRe = new RegExp('\\{\\{' + tag + '(\\||\\}\\})', 'im');
// regex check for preexistence of tag can be skipped if in canRemove mode
if (Twinkle.tag.canRemove || !tagRe.exec(pageText)) {
// condition Twinkle.tag.article.tags[tag] to ensure that its not a custom tag
// Custom tags are assumed non-groupable, since we don't know whether MI template supports them
if (Twinkle.tag.article.tags[tag] && Twinkle.tag.multipleIssuesExceptions.indexOf(tag) === -1) {
groupableTags.push(tag);
} else {
tags.push(tag);
}
} else {
if (tag === 'Merge from' || tag === 'History merge') {
tags.push(tag);
} else {
Morebits.status.warn('Info', 'Found {{' + tag +
'}} on the article already...excluding');
// don't do anything else with merge tags
if (['Merge', 'Merge to'].indexOf(tag) !== -1) {
params.mergeTarget = params.mergeReason = params.mergeTagOther = null;
}
}
}
});
// To-be-retained existing tags that are groupable
params.tagsToRemain.forEach(function(tag) {
if (Twinkle.tag.multipleIssuesExceptions.indexOf(tag) === -1) {
groupableExistingTags.push(tag);
}
});
var miTest = /\{\{(multiple ?issues|article ?issues|mi)(?!\s*\|\s*section\s*=)[^}]+\{/im.exec(pageText);
if (miTest && groupableTags.length > 0) {
Morebits.status.info('Info', 'Adding supported tags inside existing {{multiple issues}} tag');
tagText = '';
$.each(groupableTags, addTag);
var miRegex = new RegExp('(\\{\\{\\s*' + miTest[1] + '\\s*(?:\\|(?:\\{\\{[^{}]*\\}\\}|[^{}])*)?)\\}\\}\\s*', 'im');
pageText = pageText.replace(miRegex, '$1' + tagText + '}}\n');
tagText = '';
addUngroupedTags();
} else if (params.group && !miTest && (groupableExistingTags.length + groupableTags.length) >= 2) {
Morebits.status.info('Info', 'Grouping supported tags inside {{multiple issues}}');
tagText += '{{Multiple issues|\n';
/**
* Adds newly added tags to MI
*/
var addNewTagsToMI = function() {
$.each(groupableTags, addTag);
tagText += '}}\n';
addUngroupedTags();
};
var getRedirectsFor = [];
// Reposition the tags on the page into {{multiple issues}}, if found with its
// proper name, else moves it to `getRedirectsFor` array to be handled later
groupableExistingTags.forEach(function repositionTagIntoMI(tag) {
var tag_re = new RegExp('(\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]+)?\\}\\}\\n?)');
if (tag_re.test(pageText)) {
tagText += tag_re.exec(pageText)[1];
pageText = pageText.replace(tag_re, '');
} else {
getRedirectsFor.push('Template:' + tag);
}
});
if (!getRedirectsFor.length) {
addNewTagsToMI();
return;
}
var api = new Morebits.wiki.api('Getting template redirects', {
'action': 'query',
'prop': 'linkshere',
'titles': getRedirectsFor.join('|'),
'redirects': 1,
'lhnamespace': '10', // template namespace only
'lhshow': 'redirect',
'lhlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
}, function replaceRedirectTag(apiobj) {
$(apiobj.responseXML).find('page').each(function(idx, page) {
var found = false;
$(page).find('lh').each(function(idx, el) {
var tag = $(el).attr('title').slice(9);
var tag_re = new RegExp('(\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]*)?\\}\\}\\n?)');
if (tag_re.test(pageText)) {
tagText += tag_re.exec(pageText)[1];
pageText = pageText.replace(tag_re, '');
found = true;
return false; // break out of $.each
}
});
if (!found) {
Morebits.status.warn('Info', 'Failed to find the existing {{' +
$(page).attr('title').slice(9) + '}} on the page... skip repositioning');
}
});
addNewTagsToMI();
});
api.post();
} else {
tags = tags.concat(groupableTags);
addUngroupedTags();
}
},
redirect: function redirect(pageobj) {
var params = pageobj.getCallbackParameters(),
pageText = pageobj.getPageText(),
tagRe, tagText = '', summaryText = 'Added',
tags = [], i;
for (i = 0; i < params.tags.length; i++) {
tagRe = new RegExp('(\\{\\{' + params.tags[i] + '(\\||\\}\\}))', 'im');
if (!tagRe.exec(pageText)) {
tags.push(params.tags[i]);
} else {
Morebits.status.warn('Info', 'Found {{' + params.tags[i] +
'}} on the redirect already...excluding');
}
}
var addTag = function redirectAddTag(tagIndex, tagName) {
tagText += '\n{{' + tagName;
if (tagName === 'R from alternative language') {
if (params.altLangFrom) {
tagText += '|from=' + params.altLangFrom;
}
if (params.altLangTo) {
tagText += '|to=' + params.altLangTo;
}
}
tagText += '}}';
if (tagIndex > 0) {
if (tagIndex === (tags.length - 1)) {
summaryText += ' and';
} else if (tagIndex < (tags.length - 1)) {
summaryText += ',';
}
}
summaryText += ' {{[[:' + (tagName.indexOf(':') !== -1 ? tagName : 'Template:' + tagName + '|' + tagName) + ']]}}';
};
tags.sort();
$.each(tags, addTag);
// Check for all Rcat shell redirects (from #433)
if (pageText.match(/{{(?:redr|this is a redirect|r(?:edirect)?(?:.?cat.*)?[ _]?sh)/i)) {
// Regex inspired by [[User:Kephir/gadgets/sagittarius.js]] ([[Special:PermaLink/831402893]])
var oldTags = pageText.match(/(\s*{{[A-Za-z ]+\|)((?:[^|{}]*|{{[^}]*}})+)(}})\s*/i);
pageText = pageText.replace(oldTags[0], oldTags[1] + tagText + oldTags[2] + oldTags[3]);
} else {
// Fold any pre-existing Rcats into taglist and under Rcatshell
var pageTags = pageText.match(/\n{{R(?:edirect)? .*?}}/img);
var oldPageTags = '';
if (pageTags) {
pageTags.forEach(function(pageTag) {
var pageRe = new RegExp(pageTag, 'img');
pageText = pageText.replace(pageRe, '');
oldPageTags += pageTag;
});
}
pageText += '\n{{Redirect category shell|' + tagText + oldPageTags + '\n}}';
}
summaryText += (tags.length > 0 ? ' tag' + (tags.length > 1 ? 's' : '') : '') + ' to redirect';
// avoid truncated summaries
if (summaryText.length > (499 - Twinkle.getPref('summaryAd').length)) {
summaryText = summaryText.replace(/\[\[[^|]+\|([^\]]+)\]\]/g, '$1');
}
pageobj.setPageText(pageText);
pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages'));
pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor'));
pageobj.setCreateOption('nocreate');
pageobj.save();
if (params.patrol) {
pageobj.triage();
}
},
file: function friendlytagCallbacksFile(pageobj) {
var text = pageobj.getPageText();
var params = pageobj.getCallbackParameters();
var summary = 'Adding ';
// Add maintenance tags
if (params.tags.length) {
var tagtext = '', currentTag;
$.each(params.tags, function(k, tag) {
// when other commons-related tags are placed, remove "move to Commons" tag
if (['Keep local', 'subst:ncd', 'Do not move to Commons_reason', 'Do not move to Commons',
'Now Commons'].indexOf(tag) !== -1) {
text = text.replace(/\{\{(mtc|(copy |move )?to ?commons|move to wikimedia commons|copy to wikimedia commons)[^}]*\}\}/gi, '');
}
currentTag = '{{' + (tag === 'Do not move to Commons_reason' ? 'Do not move to Commons' : tag);
switch (tag) {
case 'subst:ncd':
if (params.ncdName !== '') {
currentTag += '|1=' + params.ncdName;
}
break;
case 'Keep local':
if (params.keeplocalName !== '') {
currentTag += '|1=' + params.keeplocalName;
}
break;
case 'Rename media':
if (params.renamemediaNewname !== '') {
currentTag += '|1=' + params.renamemediaNewname;
}
if (params.renamemediaReason !== '') {
currentTag += '|2=' + params.renamemediaReason;
}
break;
case 'Cleanup image':
currentTag += '|1=' + params.cleanupimageReason;
break;
case 'Image-Poor-Quality':
currentTag += '|1=' + params.ImagePoorQualityReason;
break;
case 'Low quality chem':
currentTag += '|1=' + params.lowQualityChemReason;
break;
case 'Vector version available':
text = text.replace(/\{\{((convert to |convertto|should be |shouldbe|to)?svg|badpng|vectorize)[^}]*\}\}/gi, '');
/* falls through */
case 'PNG version available':
/* falls through */
case 'Obsolete':
currentTag += '|1=' + params[tag.replace(/ /g, '_') + 'File'];
break;
case 'Do not move to Commons_reason':
currentTag += '|reason=' + params.DoNotMoveToCommons;
break;
case 'subst:orfurrev':
// remove {{non-free reduce}} and redirects
text = text.replace(/\{\{\s*(Template\s*:\s*)?(Non-free reduce|FairUseReduce|Fairusereduce|Fair Use Reduce|Fair use reduce|Reduce size|Reduce|Fair-use reduce|Image-toobig|Comic-ovrsize-img|Non-free-reduce|Nfr|Smaller image|Nonfree reduce)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/ig, '');
currentTag += '|date={{subst:date}}';
break;
case 'Copy to Commons':
currentTag += '|human=' + mw.config.get('wgUserName');
break;
case 'Should be SVG':
currentTag += '|' + params.svgCategory;
break;
default:
break; // don't care
}
currentTag += '}}\n';
tagtext += currentTag;
summary += '{{' + tag + '}}, ';
});
if (!tagtext) {
pageobj.getStatusElement().warn('User canceled operation; nothing to do');
return;
}
text = tagtext + text;
}
pageobj.setPageText(text);
pageobj.setEditSummary(summary.substring(0, summary.length - 2) + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages'));
pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor'));
pageobj.setCreateOption('nocreate');
pageobj.save();
if (params.patrol) {
pageobj.triage();
}
}
};
Twinkle.tag.callback.evaluate = function friendlytagCallbackEvaluate(e) {
var form = e.target;
var params = {};
if (form.patrolPage) {
params.patrol = form.patrolPage.checked;
}
// Don't return null if there aren't any available tags
params.tags = form.getChecked(Twinkle.tag.mode + 'Tags') || [];
// Save values of input fields into params object. This works as quickform input
// fields within subgroups of elements with name 'articleTags' (say) have their
// name attribute as 'articleTags.' + name of the subgroup element
var name_prefix = Twinkle.tag.mode + 'Tags.';
$(form).find("[name^='" + name_prefix + "']:not(div)").each(function(idx, el) {
// el are the HTMLInputElements, el.name gives the name attribute
params[el.name.slice(name_prefix.length)] =
el.type === 'checkbox' ? form[el.name].checked : form[el.name].value;
});
switch (Twinkle.tag.mode) {
case 'article':
params.tagsToRemove = form.getUnchecked('alreadyPresentArticleTags') || [];
params.tagsToRemain = form.getChecked('alreadyPresentArticleTags') || [];
params.reason = form.reason.value.trim();
params.group = form.group.checked;
// Validation
if ((params.tags.indexOf('Merge') !== -1) || (params.tags.indexOf('Merge from') !== -1) ||
(params.tags.indexOf('Merge to') !== -1)) {
if (((params.tags.indexOf('Merge') !== -1) + (params.tags.indexOf('Merge from') !== -1) +
(params.tags.indexOf('Merge to') !== -1)) > 1) {
alert('Please select only one of {{merge}}, {{merge from}}, and {{merge to}}. If several merges are required, use {{merge}} and separate the article names with pipes (although in this case Twinkle cannot tag the other articles automatically).');
return;
}
if (!params.mergeTarget) {
alert('Please specify the title of the other article for use in the merge template.');
return;
}
if ((params.mergeTagOther || params.mergeReason) && params.mergeTarget.indexOf('|') !== -1) {
alert('Tagging multiple articles in a merge, and starting a discussion for multiple articles, is not supported at the moment. Please turn off "tag other article", and/or clear out the "reason" box, and try again.');
return;
}
}
if ((params.tags.indexOf('Not English') !== -1) && (params.tags.indexOf('Rough translation') !== -1)) {
alert('Please select only one of {{not English}} and {{rough translation}}.');
return;
}
if (params.tags.indexOf('History merge') !== -1 && params.histmergeOriginalPage.trim() === '') {
alert('You must specify a page to be merged for the {{history merge}} tag.');
return;
}
if (params.tags.indexOf('Cleanup') !== -1 && params.cleanup.trim() === '') {
alert('You must specify a reason for the {{cleanup}} tag.');
return;
}
if (params.tags.indexOf('Expand language') !== -1 && params.expandLanguageLangCode.trim() === '') {
alert('You must specify language code for the {{expand language}} tag.');
return;
}
break;
case 'file':
if (params.tags.indexOf('Cleanup image') !== -1 && params.cleanupimageReason === '') {
alert('You must specify a reason for the cleanup tag.');
return;
}
if (params.tags.indexOf('Image-Poor-Quality') !== -1 && params.ImagePoorQualityReason === '') {
alert('You must specify a reason for the {{Image-Poor-Quality}} tag');
return;
}
if (params.tags.indexOf('Low Quality Chem') !== -1 && params.lowQualityChemReason === '') {
alert('You must specify a reason for the {{Low Quality Chem}} tag');
return;
}
if ((params.tags.indexOf('Obsolete') !== -1 && params.ObsoleteFile === '') ||
(params.tags.indexOf('PNG version available') !== -1 && params.PNG_version_availableFile === '') ||
(params.tags.indexOf('Vector version available') !== -1 && params.Vector_version_availableFile === '')
) {
alert('You must specify the replacement file name for a tag in the Replacement tags list');
return;
}
if (params.tags.indexOf('Do not move to Commons_reason') !== -1 && params.DoNotMoveToCommons === '') {
alert('You must specify a reason for the {{Do not move to Commons}} tag');
return;
}
break;
case 'redirect':
break;
default:
alert('Twinkle.tag: unknown mode ' + Twinkle.tag.mode);
break;
}
// File/redirect: return if no tags selected
// Article: return if no tag is selected and no already present tag is deselected
if (params.tags.length === 0 && (Twinkle.tag.mode !== 'article' || params.tagsToRemove.length === 0)) {
alert('You must select at least one tag!');
return;
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
Morebits.wiki.actionCompleted.redirect = Morebits.pageNameNorm;
Morebits.wiki.actionCompleted.notice = 'Tagging complete, reloading article in a few seconds';
if (Twinkle.tag.mode === 'redirect') {
Morebits.wiki.actionCompleted.followRedirect = false;
}
var wikipedia_page = new Morebits.wiki.page(Morebits.pageNameNorm, 'Tagging ' + Twinkle.tag.mode);
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.tag.callbacks[Twinkle.tag.mode]);
};
})(jQuery);
// </nowiki>
81uqn050g9l8v31evg30kdy57ix1dbe
کاربر:Mamouri/twinkle/twinklediff-2020.js
2
219496
909003
2026-07-30T16:05:17Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
909003
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** twinklediff.js: Diff module
****************************************
* Mode of invocation: Tab on non-diff pages ("Last"); tabs on diff pages ("Since", "Since mine", "Current")
* Active on: Existing non-special pages
*/
Twinkle.diff = function twinklediff() {
if (mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId')) {
return;
}
Twinkle.addPortletLink(mw.util.getUrl(mw.config.get('wgPageName'), {diff: 'cur', oldid: 'prev'}), 'آخرین ویرایش', 'tw-lastdiff', 'نمایش تفاوت در آخرین ویرایش'); // localized
// Show additional tabs only on diff pages
if (mw.util.getParamValue('diff')) {
Twinkle.addPortletLink(function() {
Twinkle.diff.evaluate(false);
}, 'تفاوت از نفر قبلی', 'tw-since', 'نمایش تفاوت بین آخرین تفاوت به ویرایش کاربر قبل از آن'); // localized
Twinkle.addPortletLink(function() {
Twinkle.diff.evaluate(true);
}, 'تفاوت از ویرایش من', 'tw-sincemine', 'نمایش تفاوت بین آخرین تفاوت و آخرین ویرایش من'); // localized
var oldid = /oldid=(.+)/.exec($('#mw-diff-ntitle1').find('strong a').first().attr('href'))[1];
Twinkle.addPortletLink(mw.util.getUrl(mw.config.get('wgPageName'), {diff: 'cur', oldid: oldid}), 'تفاوت با نسخهٔ فعلی', 'tw-curdiff', 'نمایش تفاوت با نسخهٔ فعلی'); // localized
}
};
Twinkle.diff.evaluate = function twinklediffEvaluate(me) {
var user;
if (me) {
user = mw.config.get('wgUserName');
} else {
var node = document.getElementById('mw-diff-ntitle2');
if (!node) {
// nothing to do?
return;
}
user = $(node).find('a').first().text();
}
var query = {
'prop': 'revisions',
'action': 'query',
'titles': mw.config.get('wgPageName'),
'rvlimit': 1,
'rvprop': [ 'ids', 'user' ],
'rvstartid': mw.config.get('wgCurRevisionId') - 1, // i.e. not the current one
'rvuser': user
};
Morebits.status.init(document.getElementById('mw-content-text'));
var wikipedia_api = new Morebits.wiki.api('گرفتن اطلاعات مشارکتکنندهٔ اولیه', query, Twinkle.diff.callbacks.main); // localized
wikipedia_api.params = { user: user };
wikipedia_api.post();
};
Twinkle.diff.callbacks = {
main: function(self) {
var xmlDoc = self.responseXML;
var revid = $(xmlDoc).find('rev').attr('revid');
if (!revid) {
self.statelem.error('هیچ نسخهٔ قبلیتر مناسبی پیدا نشد، یا ' + self.params.user + 'تنها مشارکتکننده بودهاست. پروسه متوقف شد.'); // localized
return;
}
window.location = mw.util.getUrl(mw.config.get('wgPageName'), {
diff: mw.config.get('wgCurRevisionId'),
oldid: revid
});
}
};
})(jQuery);
// </nowiki>
riaoin0pxtakh6ulhq81tsekpf30m0d
کاربر:Mamouri/twinkle/twinkleunlink-2020.js
2
219497
909004
2026-07-30T16:05:23Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
909004
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** twinkleunlink.js: Unlink module
****************************************
* Mode of invocation: Tab ("Unlink")
* Active on: Non-special pages, except Wikipedia:Sandbox
*/
Twinkle.unlink = function twinkleunlink() {
if (mw.config.get('wgNamespaceNumber') < 0 || mw.config.get('wgPageName') === 'ویکیواژه:صفحه تمرین' || // localized
// Restrict to extended confirmed users (see #428)
(!Morebits.userIsInGroup('extendedconfirmed') && !Morebits.userIsSysop)) {
return;
}
Twinkle.addPortletLink(Twinkle.unlink.callback, 'پیوندزدایی', 'tw-unlink', 'زدودن پیوندهای درونویکی'); // localized
};
Twinkle.unlink.getChecked2 = function twinkleunlinkGetChecked2(nodelist) {
if (!(nodelist instanceof NodeList) && !(nodelist instanceof HTMLCollection)) {
return nodelist.checked ? [ nodelist.values ] : [];
}
var result = [];
for (var i = 0; i < nodelist.length; ++i) {
if (nodelist[i].checked) {
result.push(nodelist[i].values);
}
}
return result;
};
// the parameter is used when invoking unlink from admin speedy
Twinkle.unlink.callback = function(presetReason) {
var Window = new Morebits.simpleWindow(600, 440);
Window.setTitle('زدودن پیوندها' + (mw.config.get('wgNamespaceNumber') === 6 ? ' و کاربردهای پرونده' : '')); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#unlink'); // localized
var form = new Morebits.quickForm(Twinkle.unlink.callback.evaluate);
// prepend some basic documentation
var node1 = Morebits.htmlNode('code', '[[' + Morebits.pageNameNorm + '|متن پیوند]]'); // localized
var node2 = Morebits.htmlNode('code', 'متن پیوند'); // localized
node1.style.fontFamily = node2.style.fontFamily = 'monospace';
node1.style.fontStyle = node2.style.fontStyle = 'normal';
form.append({
type: 'div',
style: 'margin-bottom: 0.5em',
label: [
'این ابزار اجازه میدهد که پیوندهای ورودی به این صفحه را از صفحههای دیگر بزدایید' + // localized
(mw.config.get('wgNamespaceNumber') === 6 ? '، و/یا کاربردهای این پرونده در صفحههای دیگر را با دستور <!-- --> پنهان کنید' : '') + // localized
'. برای مثال، ', // localized
node1,
' تبدیل میشود به ', // localized
node2,
'. از این ابزار با احتیاط استفاده کنید.' // localized
]
});
form.append({
type: 'input',
name: 'reason',
label: 'دلیل: ', // localized
value: presetReason ? presetReason : '',
size: 60
});
var query;
if (mw.config.get('wgNamespaceNumber') === 6) { // File:
query = {
'action': 'query',
'list': [ 'backlinks', 'imageusage' ],
'bltitle': mw.config.get('wgPageName'),
'iutitle': mw.config.get('wgPageName'),
'bllimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
'iulimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
'blnamespace': Twinkle.getPref('unlinkNamespaces'),
'iunamespace': Twinkle.getPref('unlinkNamespaces'),
'rawcontinue': true
};
} else {
query = {
'action': 'query',
'list': 'backlinks',
'bltitle': mw.config.get('wgPageName'),
'blfilterredir': 'nonredirects',
'bllimit': 'max', // 500 is max for normal users, 5000 for bots and sysops
'blnamespace': Twinkle.getPref('unlinkNamespaces'),
'rawcontinue': true
};
}
var wikipedia_api = new Morebits.wiki.api('گرفتن پیوندها', query, Twinkle.unlink.callbacks.display.backlinks); // localized
wikipedia_api.params = { form: form, Window: Window, image: mw.config.get('wgNamespaceNumber') === 6 };
wikipedia_api.post();
var root = document.createElement('div');
root.style.padding = '15px'; // just so it doesn't look broken
Morebits.status.init(root);
wikipedia_api.statelem.status('در حال بارگیری...'); // localized
Window.setContent(root);
Window.display();
};
Twinkle.unlink.callback.evaluate = function twinkleunlinkCallbackEvaluate(event) {
var reason = event.target.reason.value;
if (!reason) {
alert('شما باید دلیل زدودن پیوندها را مشخص کنید.'); // localized
return;
}
var backlinks = [], imageusage = [];
if (event.target.backlinks) {
backlinks = Twinkle.unlink.getChecked2(event.target.backlinks);
}
if (event.target.imageusage) {
imageusage = Twinkle.unlink.getChecked2(event.target.imageusage);
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(event.target);
var pages = Morebits.array.uniq(backlinks.concat(imageusage));
var unlinker = new Morebits.batchOperation('زدودن پیوندهای ورودی' + (imageusage ? ' و موارد استفاده از پرونده' : '')); // localized
unlinker.setOption('preserveIndividualStatusLines', true);
unlinker.setPageList(pages);
var params = { reason: reason, unlinker: unlinker };
unlinker.run(function(pageName) {
var wikipedia_page = new Morebits.wiki.page(pageName, 'زدودن پیوندها در صفحهٔ «' + pageName + '»'); // localized
wikipedia_page.setBotEdit(true); // unlink considered a floody operation
var innerParams = $.extend({}, params);
innerParams.doBacklinks = backlinks && backlinks.indexOf(pageName) !== -1;
innerParams.doImageusage = imageusage && imageusage.indexOf(pageName) !== -1;
wikipedia_page.setCallbackParameters(innerParams);
wikipedia_page.load(Twinkle.unlink.callbacks.unlinkBacklinks);
});
};
Twinkle.unlink.callbacks = {
display: {
backlinks: function twinkleunlinkCallbackDisplayBacklinks(apiobj) {
var xmlDoc = apiobj.responseXML;
var havecontent = false;
var list, namespaces, i;
if (apiobj.params.image) {
var imageusage = $(xmlDoc).find('query imageusage iu');
list = [];
for (i = 0; i < imageusage.length; ++i) {
var usagetitle = imageusage[i].getAttribute('title');
list.push({ label: usagetitle, value: usagetitle, checked: true });
}
if (!list.length) {
apiobj.params.form.append({ type: 'div', label: 'هیچ موردی استفادهای از پرونده یافت نشد.' }); // localized
} else {
apiobj.params.form.append({ type: 'header', label: 'کاربردهای پرونده' }); // localized
namespaces = [];
$.each(Twinkle.getPref('unlinkNamespaces'), function(k, v) {
namespaces.push(v === '0' ? '(مقاله)' : mw.config.get('wgFormattedNamespaces')[v]); // localized
});
apiobj.params.form.append({
type: 'div',
label: 'فضای نام انتخابشده: ' + namespaces.join(', '), // localized
tooltip: 'با تنظیمات توینکل (در [[وو:توینکل تر]]) میتوانید این مورد را تغییر دهید' // localized
});
if ($(xmlDoc).find('query-continue').length) {
apiobj.params.form.append({
type: 'div',
label: 'اولین ' + mw.language.convertNumber(list.length.toString()) + ' کاربرد پرونده نشان داده شدهاست.' // localized
});
}
apiobj.params.form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'imageusage')).prop('checked', true);
}
});
apiobj.params.form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'imageusage')).prop('checked', false);
}
});
apiobj.params.form.append({
type: 'checkbox',
name: 'imageusage',
list: list
});
havecontent = true;
}
}
var backlinks = $(xmlDoc).find('query backlinks bl');
if (backlinks.length > 0) {
list = [];
for (i = 0; i < backlinks.length; ++i) {
var title = backlinks[i].getAttribute('title');
list.push({ label: title, value: title, checked: true });
}
apiobj.params.form.append({ type: 'header', label: 'پیوندهای ورودی' });
namespaces = [];
$.each(Twinkle.getPref('unlinkNamespaces'), function(k, v) {
namespaces.push(v === '0' ? '(مقاله)' : mw.config.get('wgFormattedNamespaces')[v]); // localized
});
apiobj.params.form.append({
type: 'div',
label: 'فضاهای نام انتخابشده: ' + namespaces.join(', '), // localized
tooltip: 'با تنظیمات توینکل (در [[وو:توینکل تر]]) میتوانید این مورد را تغییر دهید' // localized
});
if ($(xmlDoc).find('query-continue').length) {
apiobj.params.form.append({
type: 'div',
label: 'اولین ' + mw.language.convertNumber(list.length.toString()) + ' پیوندهای ورودی نشان داده شدهاند.' // localized
});
}
apiobj.params.form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'backlinks')).prop('checked', true);
}
});
apiobj.params.form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'backlinks')).prop('checked', false);
}
});
apiobj.params.form.append({
type: 'checkbox',
name: 'backlinks',
list: list
});
havecontent = true;
} else {
apiobj.params.form.append({ type: 'div', label: 'پیوند ورودی یافت نشد.' }); // localized
}
if (havecontent) {
apiobj.params.form.append({ type: 'submit' });
}
var result = apiobj.params.form.render();
apiobj.params.Window.setContent(result);
Morebits.checkboxShiftClickSupport($("input[name='imageusage']", result));
Morebits.checkboxShiftClickSupport($("input[name='backlinks']", result));
}
},
unlinkBacklinks: function twinkleunlinkCallbackUnlinkBacklinks(pageobj) {
var oldtext = pageobj.getPageText();
var params = pageobj.getCallbackParameters();
var wikiPage = new Morebits.wikitext.page(oldtext);
var summaryText = '', warningString = false;
var text;
// remove image usages
if (params.doImageusage) {
wikiPage.commentOutImage(mw.config.get('wgTitle'), 'پنهان شد'); // localized
text = wikiPage.getText();
// did we actually make any changes?
if (text === oldtext) {
warningString = 'کاربردی از پرونده'; // localized
} else {
summaryText = 'پنهان کردن کاربردهای پرونده'; // localized
oldtext = text;
}
}
// remove backlinks
if (params.doBacklinks) {
wikiPage.removeLink(Morebits.pageNameNorm);
text = wikiPage.getText();
// did we actually make any changes?
if (text === oldtext) {
warningString = warningString ? 'پیوند ورودی یا کاربردی از پرونده' : 'پیوند ورودی'; // localized
} else {
summaryText = (summaryText ? summaryText + ' / ' : '') + 'زدودن پیوندها به'; // localized
oldtext = text;
}
}
if (warningString) {
// nothing to do!
pageobj.getStatusElement().error("هیچ " + warningString + ' در صفحه یافت نشد.'); // localized
params.unlinker.workerFailure(pageobj);
return;
}
pageobj.setPageText(text);
pageobj.setEditSummary(summaryText + ' «' + Morebits.pageNameNorm + '»: ' + params.reason + '.' + Twinkle.getPref('summaryAd')); // localized
pageobj.setCreateOption('nocreate');
pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
}
};
})(jQuery);
// </nowiki>
5j3vztqpeqw5oiy3i4g9guok32hprj4
کاربر:Mamouri/twinkle/twinklefluff-2020.js
2
219498
909005
2026-07-30T16:05:30Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
909005
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** twinklefluff.js: Revert/rollback module
****************************************
* Mode of invocation: Links on contributions, recent changes, history, and diff pages
* Active on: Diff pages, history pages, Special:RecentChanges(Linked),
and Special:Contributions
*/
/**
Twinklefluff revert and antivandalism utility
*/
Twinkle.fluff = function twinklefluff() {
// A list of usernames, usually only bots, that vandalism revert is jumped over; that is,
// if vandalism revert was chosen on such username, then its target is on the revision before.
// This is for handling quick bots that makes edits seconds after the original edit is made.
// This only affects vandalism rollback; for good faith rollback, it will stop, indicating a bot
// has no faith, and for normal rollback, it will rollback that edit.
Twinkle.fluff.whiteList = [
// localized
];
if (mw.config.get('wgIsProbablyEditable') && mw.config.get('wgNamespaceNumber') !== 2600) { // localized [exclude Flow pages]
// Only proceed if the user can actually edit the page
// in question (ignored for contributions, see #632).
// wgIsProbablyEditable should take care of
// namespace/contentModel restrictions as well as
// explicit protections; it won't take care of
// cascading or TitleBlacklist restrictions
if (mw.config.get('wgDiffNewId') || mw.config.get('wgDiffOldId')) { // wgDiffOldId included for clarity in if else loop [[phab:T214985]]
mw.hook('wikipage.diff').add(function () { // Reload alongside the revision slider
Twinkle.fluff.addLinks.diff();
});
} else if (mw.config.get('wgAction') === 'view' && mw.config.get('wgCurRevisionId') !== mw.config.get('wgRevisionId')) {
Twinkle.fluff.addLinks.oldid();
} else if (mw.config.get('wgAction') === 'history') {
Twinkle.fluff.addLinks.history();
}
} else if (mw.config.get('wgNamespaceNumber') === -1) {
Twinkle.fluff.skipTalk = !Twinkle.getPref('openTalkPageOnAutoRevert');
Twinkle.fluff.rollbackInPlace = Twinkle.getPref('rollbackInPlace');
if (mw.config.get('wgCanonicalSpecialPageName') === 'Contributions') {
Twinkle.fluff.addLinks.contributions();
} else if (mw.config.get('wgCanonicalSpecialPageName') === 'Recentchanges' || mw.config.get('wgCanonicalSpecialPageName') === 'Recentchangeslinked') {
// Reload with recent changes updates
// structuredChangeFilters.ui.initialized is just on load
mw.hook('wikipage.content').add(function(item) {
if (item.is('div')) {
Twinkle.fluff.addLinks.recentchanges();
}
});
}
}
};
Twinkle.fluff.skipTalk = null;
Twinkle.fluff.rollbackInPlace = null;
// Consolidated construction of fluff links
Twinkle.fluff.linkBuilder = {
spanTag: function(color, content) {
var span = document.createElement('span');
span.style.color = color;
span.appendChild(document.createTextNode(content));
return span;
},
buildLink: function(color, text) {
var link = document.createElement('a');
link.appendChild(Twinkle.fluff.linkBuilder.spanTag('Black', '['));
link.appendChild(Twinkle.fluff.linkBuilder.spanTag(color, text));
link.appendChild(Twinkle.fluff.linkBuilder.spanTag('Black', ']'));
link.href = '#';
return link;
},
/**
* @param {string} vandal - Username of the editor being reverted (required)
* @param {boolean} inline - True to create two links in a span, false
* to create three links in a div (optional)
* @param {number|string} [rev=wgCurRevisionId] - Revision ID being reverted (optional)
* @param {string} [page=wgPageName] - Page being reverted (optional)
*/
rollbackLinks: function(vandal, inline, rev, page) {
var elem = inline ? 'span' : 'div';
var revNode = document.createElement(elem);
rev = parseInt(rev, 10);
if (rev) {
revNode.setAttribute('id', 'tw-revert' + rev);
} else {
revNode.setAttribute('id', 'tw-revert');
}
var normNode = document.createElement('strong');
var vandNode = document.createElement('strong');
var normLink = Twinkle.fluff.linkBuilder.buildLink('SteelBlue', 'برگردانی'); // localiezd
var vandLink = Twinkle.fluff.linkBuilder.buildLink('Red', 'خرابکاری'); // localized
$(normLink).click(function() {
Twinkle.fluff.revert('norm', vandal, rev, page);
Twinkle.fluff.disableLinks(revNode);
});
$(vandLink).click(function() {
Twinkle.fluff.revert('vand', vandal, rev, page);
Twinkle.fluff.disableLinks(revNode);
});
vandNode.appendChild(vandLink);
normNode.appendChild(normLink);
var separator = inline ? ' ' : ' || ';
if (!inline) {
var agfNode = document.createElement('strong');
var agfLink = Twinkle.fluff.linkBuilder.buildLink('DarkOliveGreen', 'برگردانی (فحن)'); // localized
$(agfLink).click(function() {
Twinkle.fluff.revert('agf', vandal, rev, page);
// Twinkle.fluff.disableLinks(revNode); // rollbackInPlace not relevant for any inline situations
});
agfNode.appendChild(agfLink);
revNode.appendChild(agfNode);
}
revNode.appendChild(document.createTextNode(separator));
revNode.appendChild(normNode);
revNode.appendChild(document.createTextNode(separator));
revNode.appendChild(vandNode);
return revNode;
},
// Build [restore this revision] links
restoreThisRevisionLink: function(revisionRef, inline) {
// If not a specific revision number, should be wgDiffNewId/wgDiffOldId/wgRevisionId
revisionRef = typeof revisionRef === 'number' ? revisionRef : mw.config.get(revisionRef);
var elem = inline ? 'span' : 'div';
var revertToRevisionNode = document.createElement(elem);
revertToRevisionNode.setAttribute('id', 'tw-revert-to-' + revisionRef);
revertToRevisionNode.style.fontWeight = 'bold';
var revertToRevisionLink = Twinkle.fluff.linkBuilder.buildLink('SaddleBrown', 'برگردانی به این نسخه'); // localized
$(revertToRevisionLink).click(function() {
Twinkle.fluff.revertToRevision(revisionRef);
});
if (inline) {
revertToRevisionNode.appendChild(document.createTextNode(' '));
}
revertToRevisionNode.appendChild(revertToRevisionLink);
return revertToRevisionNode;
}
};
Twinkle.fluff.addLinks = {
contributions: function() {
// $('sp-contributions-footer-anon-range') relies on the fmbox
// id in [[MediaWiki:Sp-contributions-footer-anon-range]] and
// is used to show rollback/vandalism links for IP ranges
if (mw.config.exists('wgRelevantUserName') || !!$('#sp-contributions-footer-anon-range')[0]) {
// Get the username these contributions are for
var username = mw.config.get('wgRelevantUserName');
if (Twinkle.getPref('showRollbackLinks').indexOf('contribs') !== -1 ||
(mw.config.get('wgUserName') !== username && Twinkle.getPref('showRollbackLinks').indexOf('others') !== -1) ||
(mw.config.get('wgUserName') === username && Twinkle.getPref('showRollbackLinks').indexOf('mine') !== -1)) {
var list = $('#mw-content-text').find('ul li:has(span.mw-uctop):has(.mw-changeslist-diff)');
list.each(function(key, current) {
// revid is also available in the href of both
// .mw-changeslist-date or .mw-changeslist-diff
var page = $(current).find('.mw-contributions-title').text();
current.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks(username, true, current.dataset.mwRevid, page));
});
}
}
},
recentchanges: function() {
if (Twinkle.getPref('showRollbackLinks').indexOf('recent') !== -1) {
// Latest and revertable (not page creations, logs, categorizations, etc.)
var list = $('.mw-changeslist .mw-changeslist-last.mw-changeslist-src-mw-edit');
// Exclude top-level header if "group changes" preference is used
// and find only individual lines or nested lines
list = list.not('.mw-rcfilters-ui-highlights-enhanced-toplevel').find('.mw-changeslist-line-inner, td.mw-enhanced-rc-nested');
list.each(function(key, current) {
var vandal = $(current).find('.mw-userlink').text();
var href = $(current).find('.mw-changeslist-diff').attr('href');
var rev = mw.util.getParamValue('diff', href);
var page = current.dataset.targetPage;
current.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks(vandal, true, rev, page));
});
}
},
history: function() {
if (Twinkle.getPref('showRollbackLinks').indexOf('history') !== -1) {
// All revs
var histList = $('#pagehistory li').toArray();
// On first page of results, so add revert/rollback
// links to the top revision
if (!$('.mw-firstlink').length) {
var first = histList.shift();
var vandal = first.querySelector('.mw-userlink').text;
first.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks(vandal, true));
}
// oldid
histList.forEach(function(rev) {
// From restoreThisRevision, non-transferable
var href = rev.querySelector('.mw-changeslist-date').href;
var oldid = parseInt(mw.util.getParamValue('oldid', href), 10);
rev.appendChild(Twinkle.fluff.linkBuilder.restoreThisRevisionLink(oldid, true));
});
}
},
diff: function() {
// Autofill user talk links on diffs with vanarticle for easy warning, but don't autowarn
var warnFromTalk = function(xtitle) {
var talkLink = $('#mw-diff-' + xtitle + '2 .mw-usertoollinks a').first();
if (talkLink.length) {
var extraParams = 'vanarticle=' + mw.util.rawurlencode(Morebits.pageNameNorm) + '&' + 'noautowarn=true';
// diffIDs for vanarticlerevid
extraParams += '&vanarticlerevid=';
extraParams += xtitle === 'otitle' ? mw.config.get('wgDiffOldId') : mw.config.get('wgDiffNewId');
var href = talkLink.attr('href');
if (href.indexOf('?') === -1) {
talkLink.attr('href', href + '?' + extraParams);
} else {
talkLink.attr('href', href + '&' + extraParams);
}
}
};
// Older revision
warnFromTalk('otitle'); // Add quick-warn link to user talk link
// Don't load if there's a single revision or weird diff (cur on latest)
if (mw.config.get('wgDiffOldId') && (mw.config.get('wgDiffOldId') !== mw.config.get('wgDiffNewId'))) {
// Add a [restore this revision] link to the older revision
var oldTitle = document.getElementById('mw-diff-otitle1').parentNode;
oldTitle.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgDiffOldId'), oldTitle.firstChild);
}
// Newer revision
warnFromTalk('ntitle'); // Add quick-warn link to user talk link
// Add either restore or rollback links to the newer revision
// Don't show if there's a single revision or weird diff (prev on first)
if (document.getElementById('differences-nextlink')) {
// Not latest revision, add [restore this revision] link to newer revision
var newTitle = document.getElementById('mw-diff-ntitle1').parentNode;
newTitle.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgDiffNewId'), newTitle.firstChild);
} else if (Twinkle.getPref('showRollbackLinks').indexOf('diff') !== -1 && mw.config.get('wgDiffOldId') && (mw.config.get('wgDiffOldId') !== mw.config.get('wgDiffNewId') || document.getElementById('differences-prevlink'))) {
var vandal = $('#mw-diff-ntitle2').find('a').first().text();
var ntitle = document.getElementById('mw-diff-ntitle1').parentNode;
ntitle.insertBefore(Twinkle.fluff.linkBuilder.rollbackLinks(vandal), ntitle.firstChild);
}
},
oldid: function() { // Add a [restore this revision] link on old revisions
var title = document.getElementById('mw-revision-info').parentNode;
title.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgRevisionId'), title.firstChild);
}
};
Twinkle.fluff.disableLinks = function disablelinks(parentNode) {
// Array.from not available in IE11 :(
$(parentNode).children().each(function(_ix, node) {
node.innerHTML = node.textContent; // Feels like cheating
$(node).css('font-weight', 'normal').css('color', 'darkgray');
});
};
Twinkle.fluff.revert = function revertPage(type, vandal, rev, page) {
if (mw.util.isIPv6Address(vandal)) {
vandal = Morebits.sanitizeIPv6(vandal);
}
var pagename = page || mw.config.get('wgPageName');
var revid = rev || mw.config.get('wgCurRevisionId');
if (Twinkle.fluff.rollbackInPlace) {
var notifyStatus = document.createElement('span');
mw.notify(notifyStatus, {
autoHide: false,
title: 'برگردانی روی ' + page, // localized
tag: 'twinklefluff_' + rev // Shouldn't be necessary given disableLink
});
Morebits.status.init(notifyStatus);
} else {
Morebits.status.init(document.getElementById('mw-content-text'));
$('#catlinks').remove();
}
var params = {
type: type,
user: vandal,
pagename: pagename,
revid: revid
};
var query = {
'action': 'query',
'prop': ['info', 'revisions', 'flagged'],
'titles': pagename,
'intestactions': 'edit',
'rvlimit': 50, // intentionally limited
'rvprop': [ 'ids', 'timestamp', 'user', 'comment' ],
'curtimestamp': '',
'meta': 'tokens',
'type': 'csrf'
};
var wikipedia_api = new Morebits.wiki.api('دریافت اطلاعات نسخهٔ قدیمیتر', query, Twinkle.fluff.callbacks.main); // localized
wikipedia_api.params = params;
wikipedia_api.post();
};
Twinkle.fluff.revertToRevision = function revertToRevision(oldrev) {
Morebits.status.init(document.getElementById('mw-content-text'));
var query = {
'action': 'query',
'prop': ['info', 'revisions'],
'titles': mw.config.get('wgPageName'),
'rvlimit': 1,
'rvstartid': oldrev,
'rvprop': [ 'ids', 'timestamp', 'user', 'comment' ],
'format': 'xml',
'curtimestamp': '',
'meta': 'tokens',
'type': 'csrf'
};
var wikipedia_api = new Morebits.wiki.api('دریافت اطلاعات نسخهٔ قدیمیتر', query, Twinkle.fluff.callbacks.toRevision); // localized
wikipedia_api.params = { rev: oldrev };
wikipedia_api.post();
};
Twinkle.fluff.userIpLink = function(user) {
return (mw.util.isIPAddress(user) ? '[[Special:Contributions/' : '[[:User:') + user + '|' + user + ']]';
};
Twinkle.fluff.callbacks = {
toRevision: function(apiobj) {
var xmlDoc = apiobj.responseXML;
var lastrevid = parseInt($(xmlDoc).find('page').attr('lastrevid'), 10);
var touched = $(xmlDoc).find('page').attr('touched');
var loadtimestamp = $(xmlDoc).find('api').attr('curtimestamp');
var csrftoken = $(xmlDoc).find('tokens').attr('csrftoken');
var revertToRevID = parseInt($(xmlDoc).find('rev').attr('revid'), 10);
var revertToUser = $(xmlDoc).find('rev').attr('user');
if (revertToRevID !== apiobj.params.rev) {
apiobj.statelem.error('نسخهای که دریافت شد با نسخهای که درخواست شد مطابقت ندارد. برگردانی متوقف شد.'); // localized
return;
}
var optional_summary = prompt('لطفاً دلیل برگردانی را توضیح دهید: ', ''); // padded out to widen prompt in Firefox // localized
if (optional_summary === null) {
apiobj.statelem.error('توسط کاربر متوقف شد.'); // localized
return;
}
var summary = Twinkle.fluff.formatSummary('به نسخهٔ ' + revertToRevID + ' از $USER برگردانده شد', revertToUser, optional_summary); // localized
var query = {
'action': 'edit',
'title': mw.config.get('wgPageName'),
'summary': summary,
'token': csrftoken,
'undo': lastrevid,
'undoafter': revertToRevID,
'basetimestamp': touched,
'starttimestamp': loadtimestamp,
'watchlist': Twinkle.getPref('watchRevertedPages').indexOf('torev') !== -1 ? 'watch' : undefined,
'minor': Twinkle.getPref('markRevertedPagesAsMinor').indexOf('torev') !== -1 ? true : undefined
};
Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
Morebits.wiki.actionCompleted.notice = 'برگردانی موفق بود'; // localized
var wikipedia_api = new Morebits.wiki.api('ذخیرهٔ محتوای برگردانی شده', query, Twinkle.fluff.callbacks.complete, apiobj.statelem); // localized
wikipedia_api.params = apiobj.params;
wikipedia_api.post();
},
main: function(apiobj) {
var xmlDoc = apiobj.responseXML;
if (typeof $(xmlDoc).find('actions').attr('edit') === 'undefined') {
self.statelem.error("Unable to edit the page, it's probably protected.");
return;
}
var lastrevid = parseInt($(xmlDoc).find('page').attr('lastrevid'), 10);
var touched = $(xmlDoc).find('page').attr('touched');
var loadtimestamp = $(xmlDoc).find('api').attr('curtimestamp');
var csrftoken = $(xmlDoc).find('tokens').attr('csrftoken');
var lastuser = $(xmlDoc).find('rev').attr('user');
var revs = $(xmlDoc).find('rev');
var statelem = apiobj.statelem;
var params = apiobj.params;
if (revs.length < 1) {
statelem.error('صفحه تنها یک نسخهٔ دیگر دارد در نتیجه برگردانی ممکن نیست.'); // localiezd
return;
}
var top = revs[0];
if (lastrevid < params.revid) {
Morebits.status.error('Error', [ 'شناسهٔ آخرین نسخه که از سرور دریافت شد ', Morebits.htmlNode('strong', lastrevid), ' بود که از شناسهٔ نسخهٔ نمایش یافته کمتر است. این میتواند نشاندهندهٔ آن باشد که نسخهٔ فعلی حذف شدهاست، سرورها تأخیر دارند، یا دادههایی که دریافت شده اشتباه هستند. برگردانی متوقف شد.' ]); // localized
return;
}
var index = 1;
if (params.revid !== lastrevid) {
Morebits.status.warn('Warning', [ 'نسخهٔ آخر ', Morebits.htmlNode('strong', lastrevid), ' است که با نسخهٔ ما مطابقت ندارد', Morebits.htmlNode('strong', params.revid) ]); // loaclized
if (lastuser === params.user) {
switch (params.type) {
case 'vand':
Morebits.status.info('Info', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', params.user), ' ایجاد شدهاست. از آنجا که فرض بر خرابکاری است، برگردانی ادامه مییابد.' ]); // localizde
break;
case 'agf':
Morebits.status.warn('Warning', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', params.user), ' ایجاد شدهاست. از آنجا که فرض بر حسن نیت است، برگردانی متوقف شد چرا که مشکل ممکن است رفع شده باشد.' ]); // localiezd
return;
default:
Morebits.status.warn('Notice', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', params.user), ' ایجاد شدهاست اما برگردانی متوقف شد.' ]); // localized
return;
}
} else if (params.type === 'vand' &&
Twinkle.fluff.whiteList.indexOf(top.getAttribute('user')) !== -1 && revs.length > 1 &&
revs[1].getAttribute('pageId') === params.revid) {
Morebits.status.info('Info', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', lastuser), ' ایجاد شده که یک ربات مورد اعتماد است. نسخهٔ پیش از آن توسط کاربر خرابکار ایجاد شده لذا برگردانی ادامه مییابد.' ]); // localized
index = 2;
} else {
Morebits.status.error('Error', [ 'آخرین نسخه توسط ', Morebits.htmlNode('strong', lastuser), ' ایجاد شده پس صفحه قاعدتاً باید برگردانی شده باشد؛ برگردانی متوقف شد.']); // localized
return;
}
}
if (Twinkle.fluff.whiteList.indexOf(params.user) !== -1) {
switch (params.type) {
case 'vand':
Morebits.status.info('Info', [ 'درخواست برگردانی خرابکاری برای ویرایش ', Morebits.htmlNode('strong', params.user), ' داده شده بود. از آنجا که این حساب، یک ربات مورد اعتماد است، فرض شد که منظور شما برگردانی خرابکاری کاربری بود که ویرایش قبل از ربات را انجام داده بود.' ]); // localized
index = 2;
params.user = revs[1].getAttribute('user');
break;
case 'agf':
Morebits.status.warn('Notice', [ 'درخواست برگردانی ویرایش ', Morebits.htmlNode('strong', params.user), ' با فرض حسن نیت داده شده بود. این حساب، یک ربات مورد اعتماد است در نتیجه برگردانی متوقف شد.' ]); // localized
return;
case 'norm':
/* falls through */
default:
var cont = confirm('برگردانی عادی انتخاب شده بود، اما بیشتر تغییرات توسط یک ربات مورد اعتماد (' + params.user + ') انجام شدهاست. آیا میخواهید به نسخهٔ پیش از تغییرات ربات برگردانی کنید؟'); // localized
if (cont) {
Morebits.status.info('Info', [ 'درخواست برگردانی عادی ویرایشهای ', Morebits.htmlNode('strong', params.user), ' داده شده بود. این حساب، یک ربات مورد اعتماد است و به خواست شما، برگردانی به نسخهٔ پیش از ویرایشهای ربات انجام شد.' ]); // localized
index = 2;
params.user = revs[1].getAttribute('user');
} else {
Morebits.status.warn('Notice', [ 'درخواست برگردانی عادی ویرایشهای ', Morebits.htmlNode('strong', params.user), ' داده شدهبود. این حساب، یک ربات مورد اعتماد است اما به خواست شما ویرایشهایش برگردانی شد.' ]); // localized
}
break;
}
}
var found = false;
var count = 0;
for (var i = index; i < revs.length; ++i) {
++count;
if (revs[i].getAttribute('user') !== params.user) {
found = i;
break;
}
}
if (!found) {
statelem.error([ 'نسخهٔ قدیمیتری یافت نشد. شاید ', Morebits.htmlNode('strong', params.user), ' تنها کاربری است که در صفحه مشارکت کرده، یا بیش از ' + mw.language.convertNumber(Twinkle.getPref('revertMaxRevisions')) + ' ویرایش پیاپی در صفحه انجام دادهاست.' ]); // localized
return;
}
if (!count) {
Morebits.status.error('Error', 'از آنجا که برگردانی ویرایشهای خنثی ممکن نیست، برگردانی متوقف شد. ممکن است ویرایش پیشتر برگردانی شده باشد اگر چه شناسهٔ ویرایش هنوز همان باشد.'); // localized
return;
}
var good_revision = revs[found];
var userHasAlreadyConfirmedAction = false;
if (params.type !== 'vand' && count > 1) {
if (!confirm(params.user + ' به طور پیاپی ' + mw.language.convertNumber(count) + ' ویرایش روی صفحه کردهاست. آیا میخواهید همهٔ آنها را برگردانی کنید؟')) { // localized
Morebits.status.info('Notice', 'برگردانی متوقف شد.'); // localized
return;
}
userHasAlreadyConfirmedAction = true;
}
params.count = count;
params.goodid = good_revision.getAttribute('revid');
params.gooduser = good_revision.getAttribute('user');
statelem.status([ 'نسخهٔ ', Morebits.htmlNode('strong', params.goodid), ' که ', Morebits.htmlNode('strong', mw.language.convertNumber(count)), ' نسخه پیشتر توسط ', Morebits.htmlNode('strong', params.gooduser) , ' ثبت شده بود']); // localized
var summary, extra_summary;
switch (params.type) {
case 'agf':
extra_summary = prompt('توضیح اختیاری برای خلاصه ویرایش: ', ''); // padded out to widen prompt in Firefox // localized
if (extra_summary === null) {
statelem.error('توسط کاربر متوقف شد.'); // localized
return;
}
userHasAlreadyConfirmedAction = true;
summary = Twinkle.fluff.formatSummary('ویرایشهای دارای [[وو:فحن|حسن نیت]] $USER برگردانی شد', params.user, extra_summary); // localized
break;
case 'vand':
summary = mw.language.convertNumber(params.count) + (params.count > 1 ? ' ویرایش' : ' ویرایش') + ' از [[Special:Contributions/' + // localized
params.user + '|' + params.user + ']] ([[User talk:' + params.user + '|بحث]]) به آخرین نسخه توسط ' + // localized
params.gooduser + ' برگردانی شد' + Twinkle.getPref('summaryAd'); // localized
break;
case 'norm':
/* falls through */
default:
if (Twinkle.getPref('offerReasonOnNormalRevert')) {
extra_summary = prompt('توضیح اختیاری برای خلاصه ویرایش: ', ''); // padded out to widen prompt in Firefox // localized
if (extra_summary === null) {
statelem.error('توسط کاربر متوقف شد.'); // localized
return;
}
userHasAlreadyConfirmedAction = true;
}
summary = Twinkle.fluff.formatSummary(mw.language.convertNumber(params.count) + (params.count > 1 ? ' ویرایش' : ' ویرایش') + // localized
' $USER برگردانی شد', params.user, extra_summary); // localized
break;
}
if (Twinkle.getPref('confirmOnFluff') && !userHasAlreadyConfirmedAction && !confirm('برگردانی صفحه: آیا مطمئن هستید؟')) { // localized
statelem.error('توسط کاربر متوقف شد.'); // localized
return;
}
// Decide whether to notify the user on success
if (!Twinkle.fluff.skipTalk && Twinkle.getPref('openTalkPage').indexOf(params.type) !== -1 &&
mw.config.get('wgUserName') !== params.user) {
params.notifyUser = true;
}
// figure out whether we need to/can review the edit
var $flagged = $(xmlDoc).find('flagged');
if ((Morebits.userIsInGroup('reviewer') || Morebits.userIsSysop) &&
$flagged.length &&
$flagged.attr('stable_revid') >= params.goodid &&
$flagged.attr('pending_since')) {
params.reviewRevert = true;
params.csrftoken = csrftoken;
}
var query = {
'action': 'edit',
'title': params.pagename,
'summary': summary,
'token': csrftoken,
'undo': lastrevid,
'undoafter': params.goodid,
'basetimestamp': touched,
'starttimestamp': loadtimestamp,
'watchlist': Twinkle.getPref('watchRevertedPages').indexOf(params.type) !== -1 ? 'watch' : undefined,
'minor': Twinkle.getPref('markRevertedPagesAsMinor').indexOf(params.type) !== -1 ? true : undefined
};
if (!Twinkle.fluff.rollbackInPlace) {
Morebits.wiki.actionCompleted.redirect = params.pagename;
}
Morebits.wiki.actionCompleted.notice = 'برگردانی موفق بود'; // localized
var wikipedia_api = new Morebits.wiki.api('در حال ذخیره کردن محتوای برگردانی شده', query, Twinkle.fluff.callbacks.complete, statelem); // localized
wikipedia_api.params = params;
wikipedia_api.post();
},
complete: function (apiobj) {
// TODO Most of this is copy-pasted from Morebits.wiki.page#fnSaveSuccess. Unify it
var xml = apiobj.getXML();
var $edit = $(xml).find('edit');
if ($(xml).find('captcha').length > 0) {
apiobj.statelem.error('برگردانی موفق نبود، چون سرور ویکی درخواست کرد که شما یک کپچا پر کنید.'); // localized
} else if ($edit.attr('nochange') === '') {
apiobj.statelem.error('نسخهای که به آن برگردانی میکنیم با نسخهٔ فعلی یکسان است؛ برگردانی متوقف شد.'); // localized
} else {
apiobj.statelem.info('done');
var params = apiobj.params;
if (params.notifyUser) { // Only from main, not from toRevision
Morebits.status.info('Info', [ 'باز کردن فرم ویرایش صفحهٔ بحث کاربر ', Morebits.htmlNode('strong', params.user) ]); // localized
var windowQuery = {
'title': 'User talk:' + params.user,
'action': 'edit',
'preview': 'yes',
'vanarticle': params.pagename.replace(/_/g, ' '),
'vanarticlerevid': params.revid,
'vanarticlegoodrevid': params.goodid,
'type': params.type,
'count': params.count
};
switch (Twinkle.getPref('userTalkPageMode')) {
case 'tab':
window.open(mw.util.getUrl('', windowQuery), '_blank');
break;
case 'blank':
window.open(mw.util.getUrl('', windowQuery), '_blank',
'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800');
break;
case 'window':
/* falls through */
default:
window.open(mw.util.getUrl('', windowQuery),
window.name === 'twinklewarnwindow' ? '_blank' : 'twinklewarnwindow',
'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800');
break;
}
}
// review the revert, if needed
if (apiobj.params.reviewRevert) {
var query = {
'action': 'review',
'revid': $edit.attr('newrevid'),
'token': apiobj.params.csrftoken,
'comment': Twinkle.getPref('summaryAd').trim()
};
var wikipedia_api = new Morebits.wiki.api('پذیرش خودکار تغییرات شما', query); // localized
wikipedia_api.post();
}
}
}
};
// builtInString should contain the string "$USER", which will be replaced
// by an appropriate user link
Twinkle.fluff.formatSummary = function(builtInString, userName, userString) {
var result = builtInString;
// append user's custom reason
if (userString) {
result += ': ' + Morebits.string.toUpperCaseFirstChar(userString);
}
result += Twinkle.getPref('summaryAd');
// find number of UTF-8 bytes the resulting string takes up, and possibly add
// a contributions or contributions+talk link if it doesn't push the edit summary
// over the 255-byte limit
var resultLen = unescape(encodeURIComponent(result.replace('$USER', ''))).length;
var contribsLink = '[[Special:Contributions/' + userName + '|' + userName + ']]';
var contribsLen = unescape(encodeURIComponent(contribsLink)).length;
if (resultLen + contribsLen <= 255) {
var talkLink = ' ([[User talk:' + userName + '|بحث]])'; // localized
if (resultLen + contribsLen + unescape(encodeURIComponent(talkLink)).length <= 255) {
result = Morebits.string.safeReplace(result, '$USER', contribsLink + talkLink);
} else {
result = Morebits.string.safeReplace(result, '$USER', contribsLink);
}
} else {
result = Morebits.string.safeReplace(result, '$USER', userName);
}
return result;
};
})(jQuery);
// </nowiki>
ryuo8gy4mw5bcswzdd805hr5x6tjod2
کاربر:Mamouri/twinkle/twinklebatchdelete-2020.js
2
219499
909006
2026-07-30T16:05:38Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
909006
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** twinklebatchdelete.js: Batch delete module (sysops only)
****************************************
* Mode of invocation: Tab ("D-batch")
* Active on: Existing non-articles, and Special:PrefixIndex
*/
Twinkle.batchdelete = function twinklebatchdelete() {
if (
Morebits.userIsSysop && (
(mw.config.get('wgCurRevisionId') && mw.config.get('wgNamespaceNumber') > 0) ||
mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex'
)
) {
Twinkle.addPortletLink(Twinkle.batchdelete.callback, 'حذف دستهجمعی', 'tw-batch', 'حذف صفحههایی که در این صفحه پیوند دارند یا در این رده قرار دارند'); // localized
}
};
Twinkle.batchdelete.unlinkCache = {};
// Has the subpages list been loaded?
var subpagesLoaded;
Twinkle.batchdelete.callback = function twinklebatchdeleteCallback() {
subpagesLoaded = false;
var Window = new Morebits.simpleWindow(600, 400);
Window.setTitle('حذف دستهجمعی'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#batchdelete'); // localized
var form = new Morebits.quickForm(Twinkle.batchdelete.callback.evaluate);
form.append({
type: 'checkbox',
list: [
{
label: 'حذف صفحهها', // localized
name: 'delete_page',
value: 'delete',
checked: true,
subgroup: {
type: 'checkbox',
list: [
{
label: 'حذف صفحهٔ بحث مرتبط (به جز صفحههای بحث کاربر)', // localized
name: 'delete_talk',
value: 'delete_talk',
checked: true
},
{
label: 'حذف تغییرمسیرها به صفحههای انتخاب شده', // localized
name: 'delete_redirects',
value: 'delete_redirects',
checked: true
},
{
label: 'حذف زیرصفحههای صفحههای انتخاب شده', // localized
name: 'delete_subpages',
value: 'delete_subpages',
checked: false,
event: Twinkle.batchdelete.callback.toggleSubpages,
subgroup: {
type: 'checkbox',
list: [
{
label: 'حذف صفحههای بحث زیرصفحههای مذکور', // localized
name: 'delete_subpage_talks',
value: 'delete_subpage_talks'
},
{
label: 'حذف تغییرمسیرها به زیرصفحههای مذکور', // localized
name: 'delete_subpage_redirects',
value: 'delete_subpage_redirects'
},
{
label: 'زدودن پیوندها به زیرصفحههای مذکور (فقط در فضای نام اصلی و درگاه)', // localized
name: 'unlink_subpages',
value: 'unlink_subpages'
}
]
}
}
]
}
},
{
label: 'زدودن پیوندها به صفحههای انتخاب شده (فقط در فضای نام اصلی و درگاه)', // localized
name: 'unlink_page',
value: 'unlink',
checked: false
},
{
label: 'حذف کاربردهای پروندههای انتخاب شده (در تمام فضاهای نام)', // localized
name: 'unlink_file',
value: 'unlink_file',
checked: true
}
]
});
form.append({
type: 'input',
name: 'reason',
label: 'دلیل: ', // localized
size: 60
});
var query = {
'action': 'query',
'prop': 'revisions|info|imageinfo',
'inprop': 'protection',
'rvprop': 'size|user'
};
// On categories
if (mw.config.get('wgNamespaceNumber') === 14) {
query.generator = 'categorymembers';
query.gcmtitle = mw.config.get('wgPageName');
query.gcmlimit = Twinkle.getPref('batchMax');
// On Special:PrefixIndex
} else if (mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex') {
query.generator = 'allpages';
query.gaplimit = Twinkle.getPref('batchMax');
if (mw.util.getParamValue('prefix')) {
query.gapnamespace = mw.util.getParamValue('namespace');
query.gapprefix = mw.util.getParamValue('prefix');
} else {
var pathSplit = decodeURIComponent(location.pathname).split('/');
if (pathSplit.length < 3 || pathSplit[2] !== 'Special:PrefixIndex') {
return;
}
var titleSplit = pathSplit[3].split(':');
query.gapnamespace = mw.config.get('wgNamespaceIds')[titleSplit[0].toLowerCase()];
if (titleSplit.length < 2 || typeof query.gapnamespace === 'undefined') {
query.gapnamespace = 0; // article namespace
query.gapprefix = pathSplit.splice(3).join('/');
} else {
pathSplit = pathSplit.splice(4);
pathSplit.splice(0, 0, titleSplit.splice(1).join(':'));
query.gapprefix = pathSplit.join('/');
}
}
// On normal pages
} else {
query.generator = 'links';
query.titles = mw.config.get('wgPageName');
query.gpllimit = Twinkle.getPref('batchMax');
}
var statusdiv = document.createElement('div');
statusdiv.style.padding = '15px'; // just so it doesn't look broken
Window.setContent(statusdiv);
Morebits.status.init(statusdiv);
Window.display();
Twinkle.batchdelete.pages = {};
var statelem = new Morebits.status('گرفتن فهرست صفحهها'); // localized
var wikipedia_api = new Morebits.wiki.api('در حال بارگیری...', query, function(apiobj) { // localized
var xml = apiobj.responseXML;
var $pages = $(xml).find('page').filter(':not([missing])'); // :not([imagerepository="shared"])
$pages.each(function(index, page) {
var $page = $(page);
var ns = $page.attr('ns');
var title = $page.attr('title');
var isRedir = $page.attr('redirect') === '';
var $editprot = $page.find('pr[type="edit"][level="sysop"]');
var isProtected = $editprot.length > 0;
var size = $page.find('rev').attr('size');
var metadata = [];
if (isRedir) {
metadata.push('تغییرمسیر'); // localized
}
if (isProtected) {
metadata.push('محافظت کامل' + // localized
($editprot.attr('expiry') === 'infinity' ? ' بیپایان' : '، انقضا در ' + new Morebits.date($editprot.attr('expiry')).calendar('utc') + ' (UTC)')); // localized
}
if (ns === '6') { // mimic what delimages used to show for files
metadata.push('بارگذاری توسط: ' + $page.find('ii').attr('user')); // localized
metadata.push('آخرین ویرایش توسط: ' + $page.find('rev').attr('user')); // localized
} else {
metadata.push(mw.language.convertNumber(size) + ' بایت'); // localized
}
Twinkle.batchdelete.pages[title] = {
label: title + (metadata.length ? ' (' + metadata.join('؛ ') + ')' : ''), // localized
value: title,
checked: true,
style: isProtected ? 'color:red' : ''
};
});
var form = apiobj.params.form;
form.append({ type: 'header', label: 'صفحهها برای حذف' }); // localized
form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function dBatchSelectAll() {
$(result).find('input[name=pages]:not(:checked)').each(function(_, e) {
e.click(); // check it, and invoke click event so that subgroup can be shown
});
// Check any unchecked subpages too
$('input[name="pages.subpages"]').prop('checked', true);
}
});
form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function dBatchDeselectAll() {
$(result).find('input[name=pages]:checked').each(function(_, e) {
e.click(); // uncheck it, and invoke click event so that subgroup can be hidden
});
}
});
form.append({
type: 'checkbox',
name: 'pages',
id: 'tw-dbatch-pages',
list: $.map(Twinkle.batchdelete.pages, function (e) {
return e;
})
});
form.append({ type: 'submit' });
var result = form.render();
apiobj.params.Window.setContent(result);
var pageCheckboxes = Morebits.quickForm.getElements(result, 'pages') || [];
pageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(pageCheckboxes);
}, statelem);
wikipedia_api.params = { form: form, Window: Window };
wikipedia_api.post();
};
function generateArrowLinks (checkbox) {
var link = Morebits.htmlNode('a', ' >');
link.setAttribute('class', 'tw-dbatch-page-link');
link.setAttribute('href', mw.util.getUrl(checkbox.value));
link.setAttribute('target', '_blank');
checkbox.nextElementSibling.append(link);
}
Twinkle.batchdelete.generateNewPageList = function(form) {
// Update the list of checked pages in Twinkle.batchdelete.pages object
var elements = form.elements.pages;
if (elements instanceof NodeList) { // if there are multiple pages
for (var i = 0; i < elements.length; ++i) {
Twinkle.batchdelete.pages[elements[i].value].checked = elements[i].checked;
}
} else if (elements instanceof HTMLInputElement) { // if there is just one page
Twinkle.batchdelete.pages[elements.value].checked = elements.checked;
}
return new Morebits.quickForm.element({
type: 'checkbox',
name: 'pages',
id: 'tw-dbatch-pages',
list: $.map(Twinkle.batchdelete.pages, function (e) {
return e;
})
}).render();
};
Twinkle.batchdelete.callback.toggleSubpages = function twDbatchToggleSubpages(e) {
var form = e.target.form;
var newPageList, pageCheckboxes, subpageCheckboxes;
if (e.target.checked) {
form.delete_subpage_redirects.checked = form.delete_redirects.checked;
form.delete_subpage_talks.checked = form.delete_talk.checked;
form.unlink_subpages.checked = form.unlink_page.checked;
// If lists of subpages were already loaded once, they are
// available without use of any API calls
if (subpagesLoaded) {
$.each(Twinkle.batchdelete.pages, function(i, el) {
// Get back the subgroup from subgroup_, where we saved it
if (el.subgroup === null && el.subgroup_) {
el.subgroup = el.subgroup_;
}
});
newPageList = Twinkle.batchdelete.generateNewPageList(form);
$('#tw-dbatch-pages').replaceWith(newPageList);
pageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages') || [];
pageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(pageCheckboxes);
subpageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages.subpages') || [];
subpageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(subpageCheckboxes);
return;
}
// Proceed with API calls to get list of subpages
var loadingText = '<strong id="dbatch-subpage-loading">در حال بارگیری... </strong>'; // localized
$(e.target).after(loadingText);
var pages = $(form.pages).map(function(i, el) {
return el.value;
}).get();
var subpageLister = new Morebits.batchOperation();
subpageLister.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
subpageLister.setPageList(pages);
subpageLister.run(function worker (pageName) {
var pageTitle = mw.Title.newFromText(pageName);
// No need to look for subpages in main/file/mediawiki space
if ([0, 6, 8].indexOf(pageTitle.namespace) > -1) {
subpageLister.workerSuccess();
return;
}
var wikipedia_api = new Morebits.wiki.api('گرفتن فهرست زیرصفحههای ' + pageName, { // localized
action: 'query',
prop: 'revisions|info|imageinfo',
generator: 'allpages',
rvprop: 'size',
inprop: 'protection',
gapprefix: pageTitle.title + '/',
gapnamespace: pageTitle.namespace,
gaplimit: 'max', // 500 is max for normal users, 5000 for bots and sysops
pageNameFull: pageName // Not used by API, but added for access in onSuccess()
}, function onSuccess(apiobj) {
var xml = apiobj.responseXML;
var $pages = $(xml).find('page');
var subpageList = [];
$pages.each(function(index, page) {
var $page = $(page);
var ns = $page.attr('ns');
var title = $page.attr('title');
var isRedir = $page.attr('redirect') === '';
var $editprot = $page.find('pr[type="edit"][level="sysop"]');
var isProtected = $editprot.length > 0;
var size = $page.find('rev').attr('size');
var metadata = [];
if (isRedir) {
metadata.push('تغییرمسیر'); // localized
}
if (isProtected) {
metadata.push('محافظت کامل' + // localized
($editprot.attr('expiry') === 'infinity' ? ' بیپایان' : '، انقضا در ' + new Morebits.date($editprot.attr('expiry')).calendar('utc') + ' (UTC)')); // localized
}
if (ns === '6') { // mimic what delimages used to show for files
metadata.push('بارگذاری توسط: ' + $page.find('ii').attr('user')); //localized
metadata.push('آخرین ویرایش توسط: ' + $page.find('rev').attr('user')); // localized
} else {
metadata.push(mw.language.convertNumber(size) + ' بایت'); // localized
}
subpageList.push({
label: title + (metadata.length ? ' (' + metadata.join('؛ ') + ')' : ''), // localized
value: title,
checked: true,
style: isProtected ? 'color:red' : ''
});
});
if (subpageList.length) {
var pageName = apiobj.query.pageNameFull;
Twinkle.batchdelete.pages[pageName].subgroup = {
type: 'checkbox',
name: 'subpages',
className: 'dbatch-subpages',
list: subpageList
};
}
subpageLister.workerSuccess();
}, null /* statusElement */, function onFailure() {
subpageLister.workerFailure();
});
wikipedia_api.post();
}, function postFinish () {
// List 'em on the interface
newPageList = Twinkle.batchdelete.generateNewPageList(form);
$('#tw-dbatch-pages').replaceWith(newPageList);
pageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages') || [];
pageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(pageCheckboxes);
subpageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages.subpages') || [];
subpageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(subpageCheckboxes);
subpagesLoaded = true;
// Remove "Loading... " text
$('#dbatch-subpage-loading').remove();
});
} else if (!e.target.checked) {
$.each(Twinkle.batchdelete.pages, function(i, el) {
if (el.subgroup) {
// Remove subgroup after saving its contents in subgroup_
// so that it can be retrieved easily if user decides to
// delete the subpages again
el.subgroup_ = el.subgroup;
el.subgroup = null;
}
});
newPageList = Twinkle.batchdelete.generateNewPageList(form);
$('#tw-dbatch-pages').replaceWith(newPageList);
pageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages') || [];
pageCheckboxes.forEach(generateArrowLinks);
Morebits.checkboxShiftClickSupport(pageCheckboxes);
}
};
Twinkle.batchdelete.callback.evaluate = function twinklebatchdeleteCallbackEvaluate(event) {
Morebits.wiki.actionCompleted.notice = 'حذف دستهجمعی کامل شد'; // localized
var form = event.target;
var numProtected = $(Morebits.quickForm.getElements(form, 'pages')).filter(function(index, element) {
return element.checked && element.nextElementSibling.style.color === 'red';
}).length;
if (numProtected > 0 && !confirm('شما در حال حذف ' + mw.language.convertNumber(numProtected) + ' صفحهٔ کاملاً محافظتشده هستید. آیا مطمئن هستید؟')) { // localized
return;
}
var pages = form.getChecked('pages');
var subpages = form.getChecked('pages.subpages');
var reason = form.reason.value;
var delete_page = form.delete_page.checked;
var delete_talk, delete_redirects, delete_subpages;
var delete_subpage_redirects, delete_subpage_talks, unlink_subpages;
if (delete_page) {
delete_talk = form.delete_talk.checked;
delete_redirects = form.delete_redirects.checked;
delete_subpages = form.delete_subpages.checked;
if (delete_subpages) {
delete_subpage_redirects = form.delete_subpage_redirects.checked;
delete_subpage_talks = form.delete_subpage_talks.checked;
unlink_subpages = form.unlink_subpages.checked;
}
}
var unlink_page = form.unlink_page.checked;
var unlink_file = form.unlink_file.checked;
if (!reason) {
alert('شما باید دلیلی ارائه کنید!'); // localized
return;
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
if (!pages) {
Morebits.status.error('Error', 'چیزی برای حذف وجود نداشت؛ حذف متوقف شد'); // localized
return;
}
var pageDeleter = new Morebits.batchOperation(delete_page ? 'حذف صفحهها' : 'شروع وظایف درخواستشده'); // localized
pageDeleter.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
// we only need the initial status lines if we're deleting the pages in the pages array
pageDeleter.setOption('preserveIndividualStatusLines', delete_page);
pageDeleter.setPageList(pages);
pageDeleter.run(function worker(pageName) {
var params = {
page: pageName,
delete_page: delete_page,
delete_talk: delete_talk,
delete_redirects: delete_redirects,
unlink_page: unlink_page,
unlink_file: unlink_file && /^(File|Image|پرونده|تصویر):/i.test(pageName), // localized
reason: reason,
pageDeleter: pageDeleter
};
var wikipedia_page = new Morebits.wiki.page(pageName, 'حذف صفحهٔ ' + pageName); // localized
wikipedia_page.setCallbackParameters(params);
if (delete_page) {
wikipedia_page.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
wikipedia_page.suppressProtectWarning();
wikipedia_page.deletePage(Twinkle.batchdelete.callbacks.doExtras, pageDeleter.workerFailure);
} else {
Twinkle.batchdelete.callbacks.doExtras(wikipedia_page);
}
}, function postFinish() {
if (delete_subpages) {
var subpageDeleter = new Morebits.batchOperation('حذف زیرصفحهها'); // localized
subpageDeleter.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
subpageDeleter.setOption('preserveIndividualStatusLines', true);
subpageDeleter.setPageList(subpages);
subpageDeleter.run(function(pageName) {
var params = {
page: pageName,
delete_page: true,
delete_talk: delete_subpage_talks,
delete_redirects: delete_subpage_redirects,
unlink_page: unlink_subpages,
unlink_file: false,
reason: reason,
pageDeleter: subpageDeleter
};
var wikipedia_page = new Morebits.wiki.page(pageName, 'حذف زیرصفحه با عنوان ' + pageName); // localized
wikipedia_page.setCallbackParameters(params);
wikipedia_page.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
wikipedia_page.suppressProtectWarning();
wikipedia_page.deletePage(Twinkle.batchdelete.callbacks.doExtras, pageDeleter.workerFailure);
});
}
});
};
Twinkle.batchdelete.callbacks = {
// this stupid parameter name is a temporary thing until I implement an overhaul
// of Morebits.wiki.* callback parameters
doExtras: function(thingWithParameters) {
var params = thingWithParameters.parent ? thingWithParameters.parent.getCallbackParameters() :
thingWithParameters.getCallbackParameters();
// the initial batch operation's job is to delete the page, and that has
// succeeded by now
params.pageDeleter.workerSuccess(thingWithParameters);
var query, wikipedia_api;
if (params.unlink_page) {
Twinkle.batchdelete.unlinkCache = {};
query = {
'action': 'query',
'list': 'backlinks',
'blfilterredir': 'nonredirects',
'blnamespace': [0, 100], // main space and portal space only
'bltitle': params.page,
'bllimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
wikipedia_api = new Morebits.wiki.api('گرفتن پیوندهای ورودی', query, Twinkle.batchdelete.callbacks.unlinkBacklinksMain); // localized
wikipedia_api.params = params;
wikipedia_api.post();
}
if (params.unlink_file) {
query = {
'action': 'query',
'list': 'imageusage',
'iutitle': params.page,
'iulimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
wikipedia_api = new Morebits.wiki.api('گرفتن پیوندها به پرونده', query, Twinkle.batchdelete.callbacks.unlinkImageInstancesMain); // localized
wikipedia_api.params = params;
wikipedia_api.post();
}
if (params.delete_page) {
if (params.delete_redirects) {
query = {
'action': 'query',
'titles': params.page,
'prop': 'redirects',
'rdlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
};
wikipedia_api = new Morebits.wiki.api('گرفتن تغییرمسیرها', query, Twinkle.batchdelete.callbacks.deleteRedirectsMain); // localized
wikipedia_api.params = params;
wikipedia_api.post();
}
if (params.delete_talk) {
var pageTitle = mw.Title.newFromText(params.page);
if (pageTitle && pageTitle.namespace % 2 === 0 && pageTitle.namespace !== 2) {
pageTitle.namespace++; // now pageTitle is the talk page title!
query = {
'action': 'query',
'titles': pageTitle.toText()
};
wikipedia_api = new Morebits.wiki.api('بررسی این که صفحهٔ بحثی وجود دارد یانه', query, Twinkle.batchdelete.callbacks.deleteTalk); // localized
wikipedia_api.params = params;
wikipedia_api.params.talkPage = pageTitle.toText();
wikipedia_api.post();
}
}
}
},
deleteRedirectsMain: function(apiobj) {
var xml = apiobj.responseXML;
var pages = $(xml).find('rd').map(function() {
return $(this).attr('title');
}).get();
if (!pages.length) {
return;
}
var redirectDeleter = new Morebits.batchOperation('حذف تغییرمسیرها به ' + apiobj.params.page); // localized
redirectDeleter.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
redirectDeleter.setPageList(pages);
redirectDeleter.run(function(pageName) {
var wikipedia_page = new Morebits.wiki.page(pageName, 'حذف ' + pageName); // localized
wikipedia_page.setEditSummary('[[وو:محس#ع۸|ع۸]]: تغییرمسیر به صفحهٔ حذف شده «' + apiobj.params.page + '»' + Twinkle.getPref('deletionSummaryAd')); // localized
wikipedia_page.deletePage(redirectDeleter.workerSuccess, redirectDeleter.workerFailure);
});
},
deleteTalk: function(apiobj) {
var xml = apiobj.responseXML;
var exists = $(xml).find('page:not([missing])').length > 0;
if (!exists) {
// no talk page; forget about it
return;
}
var page = new Morebits.wiki.page(apiobj.params.talkPage, 'حذف صفحهٔ بحث ' + apiobj.params.page); // localized
page.setEditSummary('[[وو:محس#ع۸|ع۸]]: [[راهنما: صفحه بحث|صفحهٔ بحث]] صفحهٔ حذف شده «' + apiobj.params.page + '»' + Twinkle.getPref('deletionSummaryAd')); // localized
page.deletePage();
},
unlinkBacklinksMain: function(apiobj) {
var xml = apiobj.responseXML;
var pages = $(xml).find('bl').map(function() {
return $(this).attr('title');
}).get();
if (!pages.length) {
return;
}
var unlinker = new Morebits.batchOperation('زدودن پیوندهای ورودی به ' + apiobj.params.page); // localized
unlinker.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
unlinker.setPageList(pages);
unlinker.run(function(pageName) {
var wikipedia_page = new Morebits.wiki.page(pageName, 'زدودن پیوند از ' + pageName); // localized
var params = $.extend({}, apiobj.params);
params.title = pageName;
params.unlinker = unlinker;
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkBacklinks);
});
},
unlinkBacklinks: function(pageobj) {
var params = pageobj.getCallbackParameters();
if (!pageobj.exists()) {
// we probably just deleted it, as a recursive backlink
params.unlinker.workerSuccess(pageobj);
return;
}
var text;
if (params.title in Twinkle.batchdelete.unlinkCache) {
text = Twinkle.batchdelete.unlinkCache[params.title];
} else {
text = pageobj.getPageText();
}
var old_text = text;
var wikiPage = new Morebits.wikitext.page(text);
wikiPage.removeLink(params.page);
text = wikiPage.getText();
Twinkle.batchdelete.unlinkCache[params.title] = text;
if (text === old_text) {
// Nothing to do, return
params.unlinker.workerSuccess(pageobj);
return;
}
pageobj.setEditSummary('زدودن پیوندها به صفحهٔ حذف شده ' + params.page + Twinkle.getPref('deletionSummaryAd')); // localized
pageobj.setPageText(text);
pageobj.setCreateOption('nocreate');
pageobj.setMaxConflictRetries(10);
pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
},
unlinkImageInstancesMain: function(apiobj) {
var xml = apiobj.responseXML;
var pages = $(xml).find('iu').map(function() {
return $(this).attr('title');
}).get();
if (!pages.length) {
return;
}
var unlinker = new Morebits.batchOperation('حذف پیوندهای ورودی به ' + apiobj.params.page); // localized
unlinker.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks'));
unlinker.setPageList(pages);
unlinker.run(function(pageName) {
var wikipedia_page = new Morebits.wiki.page(pageName, 'حذف کاربردهای پرونده در ' + pageName); // localized
var params = $.extend({}, apiobj.params);
params.title = pageName;
params.unlinker = unlinker;
wikipedia_page.setCallbackParameters(params);
wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkImageInstances);
});
},
unlinkImageInstances: function(pageobj) {
var params = pageobj.getCallbackParameters();
if (!pageobj.exists()) {
// we probably just deleted it, as a recursive backlink
params.unlinker.workerSuccess(pageobj);
return;
}
var image = params.page.replace(/^(?:Image|File|تصویر|پرونده):/, ''); // localized
var text;
if (params.title in Twinkle.batchdelete.unlinkCache) {
text = Twinkle.batchdelete.unlinkCache[params.title];
} else {
text = pageobj.getPageText();
}
var old_text = text;
var wikiPage = new Morebits.wikitext.page(text);
wikiPage.commentOutImage(image, 'کاربرد تصویر حذف شده پنهان شد'); // localized
text = wikiPage.getText();
Twinkle.batchdelete.unlinkCache[params.title] = text;
if (text === old_text) {
pageobj.getStatusElement().error('حذف کاربرد ' + image + ' از ' + pageobj.getPageName() + ' ناموفق بود'); // localized
params.unlinker.workerFailure(pageobj);
return;
}
pageobj.setEditSummary('حذف کاربردهای ' + image + ' که حذف شدهاست؛ دلیل حذف: ' + params.reason + Twinkle.getPref('deletionSummaryAd')); // localized
pageobj.setPageText(text);
pageobj.setCreateOption('nocreate');
pageobj.setMaxConflictRetries(10);
pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure);
}
};
})(jQuery);
// </nowiki>
b3ebt8toj1p2oc3qdj7evharewom5ss
کاربر:Mamouri/twinkle/twinklebatchprotect-2020.js
2
219500
909007
2026-07-30T16:05:44Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
909007
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** twinklebatchprotect.js: Batch protect module (sysops only)
****************************************
* Mode of invocation: Tab ("P-batch")
* Active on: Existing project pages and user pages; existing and
* non-existing categories; Special:PrefixIndex
*/
Twinkle.batchprotect = function twinklebatchprotect() {
if (Morebits.userIsSysop && ((mw.config.get('wgArticleId') > 0 && (mw.config.get('wgNamespaceNumber') === 2 ||
mw.config.get('wgNamespaceNumber') === 4)) || mw.config.get('wgNamespaceNumber') === 14 ||
mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex')) {
Twinkle.addPortletLink(Twinkle.batchprotect.callback, 'محافظت دستهجمعی', 'tw-pbatch', 'محافظت صفحههایی که از این صفحه پیوند دارند'); // localized
}
};
Twinkle.batchprotect.unlinkCache = {};
Twinkle.batchprotect.callback = function twinklebatchprotectCallback() {
var Window = new Morebits.simpleWindow(600, 400);
Window.setTitle('محافظت دستهجمعی'); // localized
Window.setScriptName('توینکل'); // localized
Window.addFooterLink('سیاست حفاظت از صفحهها', 'وو:حفاظت'); // localized
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#protect'); // localized
var form = new Morebits.quickForm(Twinkle.batchprotect.callback.evaluate);
form.append({
type: 'checkbox',
name: 'editmodify',
event: Twinkle.protect.formevents.editmodify,
list: [
{
label: 'تغییر حفاظت در برابر ویرایش', // localized
value: 'editmodify',
tooltip: 'فقط برای صفحههای از پیش موجود.', // localized
checked: true
}
]
});
var editlevel = form.append({
type: 'select',
name: 'editlevel',
label: 'حفاظت در برابر ویرایش:', // localized
event: Twinkle.protect.formevents.editlevel
});
editlevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
editlevel.append({
type: 'option',
label: 'تأییدشدهٔ خودکار', // localized
value: 'autoconfirmed'
});
editlevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed'
});
editlevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
editlevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
form.append({
type: 'select',
name: 'editexpiry',
label: 'زمان سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', selected: true, value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'سفارشی...', value: 'custom' } // localized
]
});
form.append({
type: 'checkbox',
name: 'movemodify',
event: Twinkle.protect.formevents.movemodify,
list: [
{
label: 'تغییر حفاظت در برابر انتقال', // localized
value: 'movemodify',
tooltip: 'فقط برای صفحههای از پیش موجود.', // localized
checked: true
}
]
});
var movelevel = form.append({
type: 'select',
name: 'movelevel',
label: 'حفاظت در برابر انتقال:', // localized
event: Twinkle.protect.formevents.movelevel
});
movelevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
movelevel.append({
type: 'option',
label: 'تأییدشدهٔ پایدار', // localized
value: 'extendedconfirmed'
});
movelevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
movelevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
form.append({
type: 'select',
name: 'moveexpiry',
label: 'زمان سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', selected: true, value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', value: 'indefinite' }, // localized
{ label: 'سفارشی...', value: 'custom' } // localized
]
});
form.append({
type: 'checkbox',
name: 'createmodify',
event: function twinklebatchprotectFormCreatemodifyEvent(e) {
e.target.form.createlevel.disabled = !e.target.checked;
e.target.form.createexpiry.disabled = !e.target.checked || (e.target.form.createlevel.value === 'all');
e.target.form.createlevel.style.color = e.target.form.createexpiry.style.color = e.target.checked ? '' : 'transparent';
},
list: [
{
label: 'تغییر حفاظت در برابر ایجاد', // localized
value: 'createmodify',
tooltip: 'فقط برای صفحههایی که موجود نیستند.', // localized
checked: true
}
]
});
var createlevel = form.append({
type: 'select',
name: 'createlevel',
label: 'حفاظت در برابر ایجاد:', // localized
event: Twinkle.protect.formevents.createlevel
});
createlevel.append({
type: 'option',
label: 'همه', // localized
value: 'all'
});
createlevel.append({
type: 'option',
label: 'تأییدشده خودکار', // localized
value: 'autoconfirmed'
});
createlevel.append({
type: 'option',
label: 'تأییدشده پایدار', // localized
value: 'extendedconfirmed'
});
createlevel.append({
type: 'option',
label: 'ویرایشگر الگو', // localized
value: 'templateeditor'
});
createlevel.append({
type: 'option',
label: 'مدیر', // localized
value: 'sysop',
selected: true
});
form.append({
type: 'select',
name: 'createexpiry',
label: 'زمان سرآمدن:', // localized
event: function(e) {
if (e.target.value === 'custom') {
Twinkle.protect.doCustomExpiry(e.target);
}
},
list: [
{ label: '۱ ساعت', value: '1 hour' }, // localized
{ label: '۲ ساعت', value: '2 hours' }, // localized
{ label: '۳ ساعت', value: '3 hours' }, // localized
{ label: '۶ ساعت', value: '6 hours' }, // localized
{ label: '۱۲ ساعت', value: '12 hours' }, // localized
{ label: '۱ روز', value: '1 day' }, // localized
{ label: '۲ روز', value: '2 days' }, // localized
{ label: '۳ روز', value: '3 days' }, // localized
{ label: '۴ روز', value: '4 days' }, // localized
{ label: '۱ هفته', value: '1 week' }, // localized
{ label: '۲ هفته', value: '2 weeks' }, // localized
{ label: '۱ ماه', value: '1 month' }, // localized
{ label: '۲ ماه', value: '2 months' }, // localized
{ label: '۳ ماه', value: '3 months' }, // localized
{ label: '۱ سال', value: '1 year' }, // localized
{ label: 'بیپایان', selected: true, value: 'indefinite' }, // localized
{ label: 'سفارشی...', value: 'custom' } // localized
]
});
form.append({
type: 'header',
label: '' // horizontal rule
});
form.append({
type: 'input',
name: 'reason',
label: 'دلیل: ', // localized
size: 60,
tooltip: 'برای سیاههٔ حفاظت و تاریخچهٔ صفحه.' // localized
});
var query = {
'action': 'query',
'prop': 'revisions|info',
'rvprop': 'size',
'inprop': 'protection'
};
if (mw.config.get('wgNamespaceNumber') === 14) { // categories
query.generator = 'categorymembers';
query.gcmtitle = mw.config.get('wgPageName');
query.gcmlimit = Twinkle.getPref('batchMax');
} else if (mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex') {
query.generator = 'allpages';
query.gapnamespace = mw.util.getParamValue('namespace') || $('select[name=namespace]').val();
query.gapprefix = mw.util.getParamValue('prefix') || $('input[name=prefix]').val();
query.gaplimit = Twinkle.getPref('batchMax');
} else {
query.generator = 'links';
query.titles = mw.config.get('wgPageName');
query.gpllimit = Twinkle.getPref('batchMax');
}
var statusdiv = document.createElement('div');
statusdiv.style.padding = '15px'; // just so it doesn't look broken
Window.setContent(statusdiv);
Morebits.status.init(statusdiv);
Window.display();
var statelem = new Morebits.status('گرفتن فهرست صفحهها'); // localized
var wikipedia_api = new Morebits.wiki.api('در حال بارگیری...', query, function(apiobj) { // localized
var xml = apiobj.responseXML;
var $pages = $(xml).find('page');
var list = [];
$pages.each(function(index, page) {
var $page = $(page);
var title = $page.attr('title');
var isRedir = $page.attr('redirect') === ''; // XXX ??
var missing = $page.attr('missing') === ''; // XXX ??
var size = $page.find('rev').attr('size');
var $editProt;
var metadata = [];
if (missing) {
metadata.push('صفحهٔ ناموجود'); // localized
$editProt = $page.find('pr[type="create"][level="sysop"]');
} else {
if (isRedir) {
metadata.push('تغییرمسیر'); // localized
}
metadata.push(mw.language.convertNumber(size) + ' بایت'); // localized
$editProt = $page.find('pr[type="edit"][level="sysop"]');
}
if ($editProt.length > 0) {
metadata.push('محافظت کامل' + (missing ? ' در برابر ایجاد' : '') + // localized
($editProt.attr('expiry') === 'infinity' ? ' بیپایان' : '، سرآمدن در ' + new Morebits.date($editProt.attr('expiry')).calendar('utc') + ' (UTC)')); // localized
}
list.push({ label: title + (metadata.length ? ' (' + metadata.join('; ') + ')' : ''), value: title, checked: true, style: $editProt.length > 0 ? 'color:red' : '' });
});
form.append({ type: 'header', label: 'صفحههایی که محافظت خواهند شد' }); // localized
form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', true);
}
});
form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', false);
}
});
form.append({
type: 'checkbox',
name: 'pages',
list: list
});
form.append({ type: 'submit' });
var result = form.render();
Window.setContent(result);
Morebits.checkboxShiftClickSupport(Morebits.quickForm.getElements(result, 'pages'));
}, statelem);
wikipedia_api.post();
};
Twinkle.batchprotect.currentProtectCounter = 0;
Twinkle.batchprotect.currentprotector = 0;
Twinkle.batchprotect.callback.evaluate = function twinklebatchprotectCallbackEvaluate(event) {
Morebits.wiki.actionCompleted.notice = 'محافظت دستهجمعی کامل شد'; // localized
var form = event.target;
var numProtected = $(Morebits.quickForm.getElements(form, 'pages')).filter(function(index, element) {
return element.checked && element.nextElementSibling.style.color === 'red';
}).length;
if (numProtected > 0 && !confirm('شما در حال اعمال تغییرات بر روی' + mw.language.convertNumber(numProtected) + ' صفحهٔ کاملاً حفاظتشده هستید. آیا مطمئن هستید؟')) { // localized
return;
}
var pages = form.getChecked('pages');
var reason = form.reason.value;
var editmodify = form.editmodify.checked;
var editlevel = form.editlevel.value;
var editexpiry = form.editexpiry.value;
var movemodify = form.movemodify.checked;
var movelevel = form.movelevel.value;
var moveexpiry = form.moveexpiry.value;
var createmodify = form.createmodify.checked;
var createlevel = form.createlevel.value;
var createexpiry = form.createexpiry.value;
if (!reason) {
alert("باید دلیلی برای کارتان ارائه کنید!"); // localized
return;
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(form);
if (!pages) {
Morebits.status.error('Error', 'چیزی برای حفاظت موجود نیست؛ در حال لغو کردن'); // localized
return;
}
var batchOperation = new Morebits.batchOperation('اعمال تنظیمات محافظت'); // localized
batchOperation.setOption('chunkSize', Twinkle.getPref('batchProtectChunks'));
batchOperation.setOption('preserveIndividualStatusLines', true);
batchOperation.setPageList(pages);
batchOperation.run(function(pageName) {
var query = {
'action': 'query',
'titles': pageName
};
var wikipedia_api = new Morebits.wiki.api('بررسی موجود بودن صفحه' + pageName + '', query, // localized
Twinkle.batchprotect.callbacks.main, null, batchOperation.workerFailure);
wikipedia_api.params = {
page: pageName,
reason: reason,
editmodify: editmodify,
editlevel: editlevel,
editexpiry: editexpiry,
movemodify: movemodify,
movelevel: movelevel,
moveexpiry: moveexpiry,
createmodify: createmodify,
createlevel: createlevel,
createexpiry: createexpiry,
batchOperation: batchOperation
};
wikipedia_api.post();
});
};
Twinkle.batchprotect.callbacks = {
main: function(apiobj) {
var xml = apiobj.responseXML;
var normal = $(xml).find('normalized n').attr('to');
if (normal) {
apiobj.params.page = normal;
}
var exists = $(xml).find('page').attr('missing') !== '';
var page = new Morebits.wiki.page(apiobj.params.page, 'محافظت ' + apiobj.params.page); // localized
var takenAction = false;
if (exists && apiobj.params.editmodify) {
page.setEditProtection(apiobj.params.editlevel, apiobj.params.editexpiry);
takenAction = true;
}
if (exists && apiobj.params.movemodify) {
page.setMoveProtection(apiobj.params.movelevel, apiobj.params.moveexpiry);
takenAction = true;
}
if (!exists && apiobj.params.createmodify) {
page.setCreateProtection(apiobj.params.createlevel, apiobj.params.createexpiry);
takenAction = true;
}
if (!takenAction) {
Morebits.status.warn('محافظت صفحهٔ ' + apiobj.params.page, 'از پیش وجود ' + (exists ? 'دارد' : 'ندارد') + '؛ چیزی برای انجام دادن باقی نماندهاست'); // localizing
apiobj.params.batchOperation.workerFailure(apiobj);
return;
}
page.setEditSummary(apiobj.params.reason);
page.protect(apiobj.params.batchOperation.workerSuccess, apiobj.params.batchOperation.workerFailure);
}
};
})(jQuery);
// </nowiki>
5gk0xy3sp0tfgytnt8bzju1cf2tlnr8
کاربر:Mamouri/twinkle/twinklebatchundelete-2020.js
2
219501
909008
2026-07-30T16:05:52Z
Mamouri
34239
مرحلهبندی پودمان توینکل ویکیواژه
909008
javascript
text/javascript
// <nowiki>
(function($) {
/*
****************************************
*** twinklebatchundelete.js: Batch undelete module
****************************************
* Mode of invocation: Tab ("Und-batch")
* Active on: Existing user and project pages
*/
Twinkle.batchundelete = function twinklebatchundelete() {
if (!Morebits.userIsSysop || !mw.config.get('wgArticleId') || (
mw.config.get('wgNamespaceNumber') !== mw.config.get('wgNamespaceIds').user &&
mw.config.get('wgNamespaceNumber') !== mw.config.get('wgNamespaceIds').project)) {
return;
}
Twinkle.addPortletLink(Twinkle.batchundelete.callback, 'احیای دستهجمعی', 'tw-batch-undel', "احیای چندین صفحه به طور همزمان"); // localized
};
Twinkle.batchundelete.callback = function twinklebatchundeleteCallback() {
var Window = new Morebits.simpleWindow(600, 400);
Window.setScriptName('توینکل'); // localized
Window.setTitle('احیای دستهجمعی'); // localizde
Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#batchundelete'); // localized
var form = new Morebits.quickForm(Twinkle.batchundelete.callback.evaluate);
form.append({
type: 'checkbox',
list: [
{
label: 'صفحههای بحث مرتبط با صفحههای احیا شده را در صورتی که وجود داشتهاند، احیا کن', // localized
name: 'undel_talk',
value: 'undel_talk',
checked: true
}
]
});
form.append({
type: 'input',
name: 'reason',
label: 'دلیل: ', // localized
size: 60
});
var statusdiv = document.createElement('div');
statusdiv.style.padding = '15px'; // just so it doesn't look broken
Window.setContent(statusdiv);
Morebits.status.init(statusdiv);
Window.display();
var query = {
'action': 'query',
'generator': 'links',
'prop': 'info',
'inprop': 'protection',
'titles': mw.config.get('wgPageName'),
'gpllimit': Twinkle.getPref('batchMax')
};
var statelem = new Morebits.status('تهیهٔ فهرست صفحهها'); // localized
var wikipedia_api = new Morebits.wiki.api('در حال بارگیری...', query, function(apiobj) { // localized
var xml = apiobj.responseXML;
var $pages = $(xml).find('page[missing]');
var list = [];
$pages.each(function(index, page) {
var $page = $(page);
var title = $page.attr('title');
var $editprot = $page.find('pr[type="create"][level="sysop"]');
var isProtected = $editprot.length > 0;
list.push({
label: title + (isProtected ? ' (محافظتشده در برابر ایجاد' +
($editprot.attr('expiry') === 'infinity' ? ' به صورت بیپایان' : '، انقضا در ' + new Morebits.date($editprot.attr('expiry')).calendar('utc') + ' (UTC)') + ')' : ''), // localized
value: title,
checked: true,
style: isProtected ? 'color:red' : ''
});
});
apiobj.params.form.append({ type: 'header', label: 'صفحهها برای احیا' }); // localized
apiobj.params.form.append({
type: 'button',
label: 'انتخاب همه', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', true);
}
});
apiobj.params.form.append({
type: 'button',
label: 'انتخاب هیچکدام', // localized
event: function(e) {
$(Morebits.quickForm.getElements(e.target.form, 'pages')).prop('checked', false);
}
});
apiobj.params.form.append({
type: 'checkbox',
name: 'pages',
list: list
});
apiobj.params.form.append({ type: 'submit' });
var result = apiobj.params.form.render();
apiobj.params.Window.setContent(result);
Morebits.checkboxShiftClickSupport(Morebits.quickForm.getElements(result, 'pages'));
}, statelem);
wikipedia_api.params = { form: form, Window: Window };
wikipedia_api.post();
};
Twinkle.batchundelete.callback.evaluate = function(event) {
Morebits.wiki.actionCompleted.notice = 'احیای دستهجمعی کامل شد'; // localized
var numProtected = $(Morebits.quickForm.getElements(event.target, 'pages')).filter(function(index, element) {
return element.checked && element.nextElementSibling.style.color === 'red';
}).length;
if (numProtected > 0 && !confirm('شما در حال احیای ' + numProtected + ' صفحه هستید که در برابر ایجاد محافظت شدهاند. آیا مطمئن هستید؟')) { // localized
return;
}
var pages = event.target.getChecked('pages');
var reason = event.target.reason.value;
var undel_talk = event.target.reason.value;
if (!reason) {
alert('باید دلیلی ارائه کنید!'); // localized
return;
}
Morebits.simpleWindow.setButtonsEnabled(false);
Morebits.status.init(event.target);
if (!pages) {
Morebits.status.error('Error', 'چیزی برای احیا موجود نیست، احیا متوقف شد'); // localized
return;
}
var pageUndeleter = new Morebits.batchOperation('Undeleting pages');
pageUndeleter.setOption('chunkSize', Twinkle.getPref('batchUndeleteChunks'));
pageUndeleter.setOption('preserveIndividualStatusLines', true);
pageUndeleter.setPageList(pages);
pageUndeleter.run(function(pageName) {
var params = {
page: pageName,
undel_talk: undel_talk,
reason: reason,
pageUndeleter: pageUndeleter
};
var wikipedia_page = new Morebits.wiki.page(pageName, 'احیای صفحهٔ ' + pageName); // localized
wikipedia_page.setCallbackParameters(params);
wikipedia_page.setEditSummary(reason + Twinkle.getPref('deletionSummaryAd'));
wikipedia_page.suppressProtectWarning();
wikipedia_page.setMaxRetries(3); // temporary increase from 2 to make batchundelete more likely to succeed [[phab:T222402]] #613
wikipedia_page.undeletePage(Twinkle.batchundelete.callbacks.doExtras, pageUndeleter.workerFailure);
});
};
Twinkle.batchundelete.callbacks = {
// this stupid parameter name is a temporary thing until I implement an overhaul
// of Morebits.wiki.* callback parameters
doExtras: function(thingWithParameters) {
var params = thingWithParameters.parent ? thingWithParameters.parent.getCallbackParameters() :
thingWithParameters.getCallbackParameters();
// the initial batch operation's job is to delete the page, and that has
// succeeded by now
params.pageUndeleter.workerSuccess(thingWithParameters);
var query, wikipedia_api;
if (params.undel_talk) {
var talkpagename = new mw.Title(params.page).getTalkPage().getPrefixedText();
if (talkpagename !== params.page) {
query = {
'action': 'query',
'prop': 'deletedrevisions',
'drvprop': 'ids',
'drvlimit': 1,
'titles': talkpagename
};
wikipedia_api = new Morebits.wiki.api('بررسی صفحهٔ بحث برای نسخههای حذفشده', query, Twinkle.batchundelete.callbacks.undeleteTalk); // localized
wikipedia_api.params = params;
wikipedia_api.params.talkPage = talkpagename;
wikipedia_api.post();
}
}
},
undeleteTalk: function(apiobj) {
var xml = apiobj.responseXML;
var exists = $(xml).find('page:not([missing])').length > 0;
var delrevs = $(xml).find('rev').attr('revid');
if (exists || !delrevs) {
// page exists or has no deleted revisions; forget about it
return;
}
var page = new Morebits.wiki.page(apiobj.params.talkPage, 'احیای صفحهٔ بحث ' + apiobj.params.page); // localized
page.setEditSummary('احیای [[راهنما:صفحه بحث|صفحهٔ بحث]] "' + apiobj.params.page + '"' + Twinkle.getPref('deletionSummaryAd')); // localized
page.undeletePage();
}
};
})(jQuery);
// </nowiki>
oc09u59zmuv0w7fha30ly52n3bhrtm1
کاربر:Mamouri/توینکل
2
219502
909009
2026-07-30T16:06:18Z
Mamouri
34239
یادداشتهای راهاندازی توینکل ویکیواژه
909009
wikitext
text/x-wiki
این صفحه یادداشتهای بومیسازی و راهاندازی '''توینکل''' برای ویکیواژهٔ فارسی است. نسخهٔ پیشین که از ویکیپدیا رونوشت شده بود ناقص و خراب بود؛ من همهٔ پودمانها را برای معیارها، الگوها و تابلوهای ویکیواژه بازنویسی و بومی کردم و آنها را در زیرصفحههای [[کاربر:Mamouri/twinkle/]] گذاشتهام.
== وضعیت ==
'''آماده و آزمودهشده:''' هستهٔ توینکل بههمراه پودمانهای حذف سریع، پیشنهاد حذف، هشدار، گزارش به مدیران، خوشامد، برچسبزنی، واگردانی و ابزارهای مدیریتی. جعبههای گفتوگو در ویکیواژه بارگذاری و آزموده شدند.
== صفحههای محتوایی که ساختهام ==
* [[ویکیواژه:محس|معیارهای حذف سریع]] و [[الگو:دست نگهدار]]
* روند [[ویکیواژه:پیشنهاد حذف]]، [[الگو:پیشنهاد حذف]] و [[:رده:پیشنهادهای حذف]]
== نصب در فضای مدیاویکی ==
پودمانهای بومیشده را از زیرصفحههای [[کاربر:Mamouri/twinkle/]] در صفحههای متناظر «مدیاویکی:Gadget-…» رونویسی کنید (این کار دسترسی «مدیر رابط» میخواهد). سپس تعریف ابزار در [[مدیاویکی:Gadgets-definition]] را با سطر زیر جایگزین کنید — دو تغییر کلیدی: دسترسی از editsitejs به autoconfirmed تغییر کرده و پودمانهای ناموجود (image و xfd) از فهرست برداشته شدهاند تا ابزار بهدرستی بارگذاری شود:
* Twinkle[ResourceLoader|dependencies=ext.gadget.select2,mediawiki.api,ext.gadget.morebits|rights=autoconfirmed|type=general|peers=Twinkle-pagestyles]|Twinkle-2020.js|twinkleconfig-2020.js|twinklewarn-2020.js|twinklearv-2020.js|twinkleblock-2020.js|friendlywelcome-2020.js|friendlyshared-2020.js|friendlytalkback-2020.js|twinklespeedy-2020.js|twinkleprod-2020.js|twinkledeprod-2020.js|twinkleprotect-2020.js|friendlytag-2020.js|twinklediff-2020.js|twinkleunlink-2020.js|twinklefluff-2020.js|twinklebatchdelete-2020.js|twinklebatchprotect-2020.js|twinklebatchundelete-2020.js|Twinkle-2020.css
== آزمایش پیش از نصب ==
برای آزمایش بدون دستزدن به ابزار عمومی، این دو سطر را به [[ویژه:صفحهٔ من/common.js]] بیفزایید:
mw.loader.load('https://fa.wiktionary.org/w/index.php?title=User:Mamouri/twinkle-bundle.css&action=raw&ctype=text/css','text/css');
mw.loader.load('https://fa.wiktionary.org/w/index.php?title=User:Mamouri/twinkle-bundle.js&action=raw&ctype=text/javascript');
== کارهای باقیمانده (فاز دوم) ==
* '''پودمان xfd (نظرخواهی برای حذف):''' نیازمند یک صفحهٔ نظرخواهیِ حذف در ویکیواژه است که هنوز وجود ندارد. چون این یک روند تازهٔ سیاستگذاری است، پیش از پیادهسازی باید در جامعه دربارهٔ آن به توافق رسید.
* '''پودمان image:''' برای برچسبزنی پروندههای محلی است؛ چون بیشتر پروندهها در ویکیانبارند، فعلاً کنار گذاشته شد.
* هماهنگسازی صفحهٔ راهنمای [[ویکیواژه:توینکل]] با میانبرهای درست «وو:».
o4d5tt4z2w1z43jyq4f779e3zqhi7o4
بحث کاربر:Aelxen
3
219503
909016
2026-07-30T22:02:30Z
پیام به کاربر جدید
26498
افزودن [[الگو:خوشامدید|پیام خوشآمد]] به صفحهٔ بحث کاربر تازه
909016
wikitext
text/x-wiki
== خوش آمدید ==
[[ویکیواژه:تازهواردان، خوش آمدید|'''به ویکیواژه خوشآمدید''']]!<br />
امیدوارم حضوری طولانی مدت در ویکیواژه داشته باشید و بتوانیم با همکاری یکدیگر در جهت پیشرفت هر چه بیشتر این وبگاه گام برداریم. ویکیواژه منتظر نوشتارهای خوب شماست.<br />
این پیوندها میتواند سودمند باشد:
{|
|-
|[[پرونده:Noia 64 apps help index.png|30px|right|link=ویکیواژه:راهنما|راهنما]] || [[ویکیواژه:راهنما|راهنما]] برای راهنمایی گرفتن
|-
| [[پرونده:Nuvola apps ksig.png|30px|right|link=ویکیواژه:آموزش سریع|آموزش سریع]] || [[ویکیواژه:آموزش سریع|آموزش سریع]]
|-
|||[[راهنما:افزودن سرواژه نو|افزودن سرواژهٔ نو]] راهنمای افزودن سرواژهٔ نو
|-
|||[[ویکیواژه:شیوهنامه|شیوهنامه]] شیوهنامهٔ ویکیواژه
|-
|[[File:Bucket in the sand.svg|right|50px]]||[[ویکیواژه:صفحه تمرین|صفحه تمرین]] برای تمرین ویرایش
|-
| [[پرونده:Nuvola apps chat.png|30px|right|link=ویکیواژه:قهوهخانه|قهوهخانه]]||[[ویکیواژه:قهوهخانه|قهوهخانه]] برای گفتگو دربارهٔ مسائل فنی و سیاستها.
|-
|[[پرونده:Farsi.svg|30px|right|link=ویکیواژه:فارسینویسی|فارسینویسی]]||[[ویکیواژه:فارسینویسی|فارسینویسی]]
|-
|[[پرونده:Nuvola apps agent.svg|30px|right|link=w:ویکیپدیا:خودآموز|آموزش پیشرفته]]||[[w:ویکیپدیا:خودآموز|آموزش پیشرفته]]
|-
|
|-
|
|-
|'''پروژههای دیگر بنیاد'''||[[پرونده:Wikipedia-logo.png|25px|link=w:صفحه_اصلی|ویکیپدیا]][[پرونده:HSWikimedia.svg|30px|link=m:Special:Recentchanges|فراویکی]] [[پرونده:HSCommons.svg|30px|link=commons:Special:Recentchanges|ویکیانبار]][[پرونده:HSWNews.svg|30px|link=n:ویژه:تغییرات اخیر|ویکیخبر]] [[پرونده:HSWBooks.svg|30px|link=b:صفحه_اصلی|ویکینَسک]] [[پرونده:HSWVoyage.svg|30px|link=voy:صفحه_اصلی|ویکیسفر]][[پرونده:HSWQuote.svg|30px|link=q:صفحه_اصلی|ویکیگفتاورد]][[پرونده:HSWSource.svg|30px|link=s:صفحه_اصلی|ویکینبشته]]
|}
امیدوارم از ''[[ویکیواژه:ویکیواژه نویسان|ویکیواژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~~~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکیواژه:قهوهخانه|قهوهخانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد میگویم! شاد باشید!
-- [[کاربر:The.shahab|The.shahab]] ([[بحث کاربر:The.shahab|بحث]]) ۷ ژوئیهٔ ۲۰۲۶، ساعت ۱۴:۴۳ (UTC) ۳۰ ژوئیهٔ ۲۰۲۶، ساعت ۲۲:۰۲ (UTC)
td5yn5d6bnmscdwoix5dfoya70i04hm
الاق
0
219504
909050
2026-07-31T11:49:23Z
Kunakes
46856
الاق/ایجاد
909050
wikitext
text/x-wiki
==فارسی==
===ریشه شناسی===
*{{ترکی}}
===آوایش===
*/اُلاق/
===[[file:Nuvola apps bookcase2.png|30px]] اسم===
'''الاق'''
#[[الاغ]]، [[خر]].
====منابع====
*فرهنگ بزرگ سخن
[[رده:اسمهای ترکی]]
[[رده:اسمهای آذری]]
argw7mt37mss4o9b7cprhoxc5lh7g5j
آلاق
0
219505
909052
2026-07-31T11:50:07Z
Kunakes
46856
Kunakes صفحهٔ [[آلاق]] را به [[آلاغ]] منتقل کرد
909052
wikitext
text/x-wiki
#تغییر_مسیر [[آلاغ]]
7uufuvf4hqmq3hesrt7e85jfa66fy84
علفهرز
0
219506
909054
2026-07-31T11:55:49Z
Kunakes
46856
علف هرز/تغییرمسیر
909054
wikitext
text/x-wiki
#تغییرمسیر [[علف هرز]]
e9v5lsg02uie65f2871t8m9xf5ged8c