ویکی‌واژه 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:صفحه_اصلی|ویکی‌نبشته]] |} امیدوارم از ''[[ویکی‌واژه:ویکی‌واژه نویسان|ویکی‌واژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~&#126;~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکی‌واژه:قهوه‌خانه|قهوه‌خانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد می‌گویم!شاد باشید! -- [[کاربر: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:صفحه_اصلی|ویکی‌نبشته]] |} امیدوارم از ''[[ویکی‌واژه:ویکی‌واژه نویسان|ویکی‌واژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~&#126;~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکی‌واژه:قهوه‌خانه|قهوه‌خانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد می‌گویم!&nbsp;شاد باشید! -- [[کاربر: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:صفحه_اصلی|ویکی‌نبشته]] |} امیدوارم از ''[[ویکی‌واژه:ویکی‌واژه نویسان|ویکی‌واژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~&#126;~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکی‌واژه:قهوه‌خانه|قهوه‌خانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد می‌گویم!&nbsp;شاد باشید! -- [[کاربر: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:صفحه_اصلی|ویکی‌نبشته]] |} امیدوارم از ''[[ویکی‌واژه:ویکی‌واژه نویسان|ویکی‌واژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~&#126;~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکی‌واژه:قهوه‌خانه|قهوه‌خانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد می‌گویم!&nbsp;شاد باشید! -- [[کاربر: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:صفحه_اصلی|ویکی‌نبشته]] |} امیدوارم از ''[[ویکی‌واژه:ویکی‌واژه نویسان|ویکی‌واژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~&#126;~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکی‌واژه:قهوه‌خانه|قهوه‌خانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد می‌گویم!&nbsp;شاد باشید! -- [[کاربر: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:صفحه_اصلی|ویکی‌نبشته]] |} امیدوارم از ''[[ویکی‌واژه:ویکی‌واژه نویسان|ویکی‌واژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~&#126;~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکی‌واژه:قهوه‌خانه|قهوه‌خانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد می‌گویم!&nbsp;شاد باشید! -- [[کاربر: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:صفحه_اصلی|ویکی‌نبشته]] |} امیدوارم از ''[[ویکی‌واژه:ویکی‌واژه نویسان|ویکی‌واژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~&#126;~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکی‌واژه:قهوه‌خانه|قهوه‌خانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد می‌گویم!&nbsp;شاد باشید! -- [[کاربر: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:صفحه_اصلی|ویکی‌نبشته]] |} امیدوارم از ''[[ویکی‌واژه:ویکی‌واژه نویسان|ویکی‌واژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~&#126;~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکی‌واژه:قهوه‌خانه|قهوه‌خانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد می‌گویم!&nbsp;شاد باشید! -- [[کاربر: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:صفحه_اصلی|ویکی‌نبشته]] |} امیدوارم از ''[[ویکی‌واژه:ویکی‌واژه نویسان|ویکی‌واژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~&#126;~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکی‌واژه:قهوه‌خانه|قهوه‌خانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد می‌گویم!&nbsp;شاد باشید! -- [[کاربر: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 = &middot; $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: "برخی الگوهای خوشامد دارای جملهٔ اولیه‌ای مانند \«سلام، من &lt;username&gt; هستم. خوش آمدید\" یا مشابه آن هستند. اگر این گزینه را غیرفعال کنید، این الگوها نام کاربری شما را به‌این نحو نمایش نخواهند داد.", // 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 + ']] &ndash; ' + 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 + ']] &ndash; '; 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> &bull; </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 + '&nbsp;</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: 'الگوی حفاظت را درون &lt;noinclude&gt; قرار می‌دهد تا تراگنجانش نشود', // 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 = &middot; $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: "برخی الگوهای خوشامد دارای جملهٔ اولیه‌ای مانند \«سلام، من &lt;username&gt; هستم. خوش آمدید\" یا مشابه آن هستند. اگر این گزینه را غیرفعال کنید، این الگوها نام کاربری شما را به‌این نحو نمایش نخواهند داد.", // 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 + ']] &ndash; ' + 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 + ']] &ndash; '; 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> &bull; </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 + '&nbsp;</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: 'الگوی حفاظت را درون &lt;noinclude&gt; قرار می‌دهد تا تراگنجانش نشود', // 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: "برخی الگوهای خوشامد دارای جملهٔ اولیه‌ای مانند \«سلام، من &lt;username&gt; هستم. خوش آمدید\" یا مشابه آن هستند. اگر این گزینه را غیرفعال کنید، این الگوها نام کاربری شما را به‌این نحو نمایش نخواهند داد.", // 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 + ']] &ndash; ' + 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 + ']] &ndash; '; 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> &bull; </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 + '&nbsp;</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: 'الگوی حفاظت را درون &lt;noinclude&gt; قرار می‌دهد تا تراگنجانش نشود', // 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:صفحه_اصلی|ویکی‌نبشته]] |} امیدوارم از ''[[ویکی‌واژه:ویکی‌واژه نویسان|ویکی‌واژه نویس]]'' بودن لذت ببرید! لطفاً در صفحات گفتگو نام خود را با چهار علامت مدک امضا کنید، (~&#126;~~)، با این کار نام شما و تاریخ به عنوان امضا درج خواهد شد. در صورتی که سوال داشتید، به صفحهٔ [[ویکی‌واژه:قهوه‌خانه|قهوه‌خانه]] بروید، یا اینکه از من در صفحهٔ بحثم بپرسید. باز هم خوش آمد می‌گویم!&nbsp;شاد باشید! -- [[کاربر: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